feat(projects): remove namespaces
This commit is contained in:
@ -47,13 +47,14 @@ type Project struct {
|
||||
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
|
||||
|
||||
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
|
||||
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"`
|
||||
ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id"`
|
||||
|
||||
ChildProjects []*Project `xorm:"-" json:"child_projects"`
|
||||
|
||||
// The user who created this project.
|
||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
||||
|
||||
// Whether or not a project is archived.
|
||||
// Whether a project is archived.
|
||||
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
|
||||
|
||||
// The id of the file this project has set as background
|
||||
@ -92,7 +93,7 @@ type ProjectWithTasksAndBuckets struct {
|
||||
}
|
||||
|
||||
// TableName returns a better name for the projects table
|
||||
func (l *Project) TableName() string {
|
||||
func (p *Project) TableName() string {
|
||||
return "projects"
|
||||
}
|
||||
|
||||
@ -104,70 +105,42 @@ type ProjectBackgroundType struct {
|
||||
// ProjectBackgroundUpload represents the project upload background type
|
||||
const ProjectBackgroundUpload string = "upload"
|
||||
|
||||
// FavoritesPseudoProject holds all tasks marked as favorites
|
||||
var FavoritesPseudoProject = Project{
|
||||
// SharedProjectsPseudoProject is a pseudo project used to hold shared projects
|
||||
var SharedProjectsPseudoProject = &Project{
|
||||
ID: -1,
|
||||
Title: "Favorites",
|
||||
Description: "This project has all tasks marked as favorites.",
|
||||
NamespaceID: FavoritesPseudoNamespace.ID,
|
||||
IsFavorite: true,
|
||||
Title: "Shared Projects",
|
||||
Description: "Projects of other users shared with you via teams or directly.",
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// GetProjectsByNamespaceID gets all projects in a namespace
|
||||
func GetProjectsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (projects []*Project, err error) {
|
||||
switch nID {
|
||||
case SharedProjectsPseudoNamespace.ID:
|
||||
nnn, err := getSharedProjectsInNamespace(s, false, doer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nnn != nil && nnn.Projects != nil {
|
||||
projects = nnn.Projects
|
||||
}
|
||||
case FavoritesPseudoNamespace.ID:
|
||||
namespaces := make(map[int64]*NamespaceWithProjects)
|
||||
_, err := getNamespacesWithProjects(s, &namespaces, "", false, 0, -1, doer.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespaceIDs, _ := getNamespaceOwnerIDs(namespaces)
|
||||
ls, err := getProjectsForNamespaces(s, namespaceIDs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nnn, err := getFavoriteProjects(s, ls, namespaceIDs, doer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nnn != nil && nnn.Projects != nil {
|
||||
projects = nnn.Projects
|
||||
}
|
||||
case SavedFiltersPseudoNamespace.ID:
|
||||
nnn, err := getSavedFilters(s, doer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nnn != nil && nnn.Projects != nil {
|
||||
projects = nnn.Projects
|
||||
}
|
||||
default:
|
||||
err = s.Select("l.*").
|
||||
Alias("l").
|
||||
Join("LEFT", []string{"namespaces", "n"}, "l.namespace_id = n.id").
|
||||
Where("l.is_archived = false").
|
||||
Where("n.is_archived = false OR n.is_archived IS NULL").
|
||||
Where("namespace_id = ?", nID).
|
||||
Find(&projects)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// FavoriteProjectsPseudoProject is a pseudo namespace used to hold favorite projects and tasks
|
||||
var FavoriteProjectsPseudoProject = &Project{
|
||||
ID: -2,
|
||||
Title: "Favorites",
|
||||
Description: "Favorite projects and tasks.",
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// get more project details
|
||||
err = addProjectDetails(s, projects, doer)
|
||||
return projects, err
|
||||
// SavedFiltersPseudoProject is a pseudo namespace used to hold saved filters
|
||||
var SavedFiltersPseudoProject = &Project{
|
||||
ID: -3,
|
||||
Title: "Filters",
|
||||
Description: "Saved filters.",
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// FavoritesPseudoProject holds all tasks marked as favorites
|
||||
var FavoritesPseudoProject = Project{
|
||||
ID: -1,
|
||||
Title: "Favorites",
|
||||
Description: "This project has all tasks marked as favorites.",
|
||||
ParentProjectID: FavoriteProjectsPseudoProject.ID,
|
||||
IsFavorite: true,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// ReadAll gets all projects a user has access to
|
||||
@ -185,7 +158,7 @@ func GetProjectsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (proj
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects [get]
|
||||
func (l *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
|
||||
func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
|
||||
// Check if we're dealing with a share auth
|
||||
shareAuth, ok := a.(*LinkSharing)
|
||||
if ok {
|
||||
@ -193,26 +166,67 @@ func (l *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
projects := []*Project{project}
|
||||
projects := map[int64]*Project{}
|
||||
projects[project.ID] = project
|
||||
err = addProjectDetails(s, projects, a)
|
||||
return projects, 0, 0, err
|
||||
}
|
||||
|
||||
projects, resultCount, totalItems, err := getRawProjectsForUser(
|
||||
doer, err := user.GetFromAuth(a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
allProjects, resultCount, totalItems, err := getRawProjectsForUser(
|
||||
s,
|
||||
&projectOptions{
|
||||
search: search,
|
||||
user: &user.User{ID: a.GetID()},
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
isArchived: l.IsArchived,
|
||||
search: search,
|
||||
user: doer,
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
getArchived: p.IsArchived,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
// Add more project details
|
||||
err = addProjectDetails(s, projects, a)
|
||||
/////////////////
|
||||
// Saved Filters
|
||||
|
||||
savedFiltersProject, err := getSavedFilterProjects(s, doer)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
if savedFiltersProject != nil {
|
||||
allProjects[savedFiltersProject.ID] = savedFiltersProject
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Add project details (favorite state, among other things)
|
||||
err = addProjectDetails(s, allProjects, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Putting it all together
|
||||
|
||||
var projects []*Project
|
||||
for _, p := range allProjects {
|
||||
if p.ParentProjectID != 0 {
|
||||
if allProjects[p.ParentProjectID].ChildProjects == nil {
|
||||
allProjects[p.ParentProjectID].ChildProjects = []*Project{}
|
||||
}
|
||||
allProjects[p.ParentProjectID].ChildProjects = append(allProjects[p.ParentProjectID].ChildProjects, p)
|
||||
continue
|
||||
}
|
||||
|
||||
// The projects variable will contain all projects which have no parents
|
||||
// And because we're using the same pointers for everything, those will contain child projects
|
||||
projects = append(projects, p)
|
||||
}
|
||||
|
||||
return projects, resultCount, totalItems, err
|
||||
}
|
||||
|
||||
@ -228,61 +242,61 @@ func (l *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects/{id} [get]
|
||||
func (l *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
|
||||
if l.ID == FavoritesPseudoProject.ID {
|
||||
if p.ID == FavoritesPseudoProject.ID {
|
||||
// Already "built" the project in CanRead
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for saved filters
|
||||
if getSavedFilterIDFromProjectID(l.ID) > 0 {
|
||||
sf, err := getSavedFilterSimpleByID(s, getSavedFilterIDFromProjectID(l.ID))
|
||||
if getSavedFilterIDFromProjectID(p.ID) > 0 {
|
||||
sf, err := getSavedFilterSimpleByID(s, getSavedFilterIDFromProjectID(p.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Title = sf.Title
|
||||
l.Description = sf.Description
|
||||
l.Created = sf.Created
|
||||
l.Updated = sf.Updated
|
||||
l.OwnerID = sf.OwnerID
|
||||
p.Title = sf.Title
|
||||
p.Description = sf.Description
|
||||
p.Created = sf.Created
|
||||
p.Updated = sf.Updated
|
||||
p.OwnerID = sf.OwnerID
|
||||
}
|
||||
|
||||
// Get project owner
|
||||
l.Owner, err = user.GetUserByID(s, l.OwnerID)
|
||||
p.Owner, err = user.GetUserByID(s, p.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if the namespace is archived and set the namespace to archived if it is not already archived individually.
|
||||
if !l.IsArchived {
|
||||
err = l.CheckIsArchived(s)
|
||||
if !p.IsArchived {
|
||||
err = p.CheckIsArchived(s)
|
||||
if err != nil {
|
||||
if !IsErrNamespaceIsArchived(err) && !IsErrProjectIsArchived(err) {
|
||||
return
|
||||
}
|
||||
l.IsArchived = true
|
||||
p.IsArchived = true
|
||||
}
|
||||
}
|
||||
|
||||
// Get any background information if there is one set
|
||||
if l.BackgroundFileID != 0 {
|
||||
if p.BackgroundFileID != 0 {
|
||||
// Unsplash image
|
||||
l.BackgroundInformation, err = GetUnsplashPhotoByFileID(s, l.BackgroundFileID)
|
||||
p.BackgroundInformation, err = GetUnsplashPhotoByFileID(s, p.BackgroundFileID)
|
||||
if err != nil && !files.IsErrFileIsNotUnsplashFile(err) {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
|
||||
l.BackgroundInformation = &ProjectBackgroundType{Type: ProjectBackgroundUpload}
|
||||
p.BackgroundInformation = &ProjectBackgroundType{Type: ProjectBackgroundUpload}
|
||||
}
|
||||
}
|
||||
|
||||
l.IsFavorite, err = isFavorite(s, l.ID, a, FavoriteKindProject)
|
||||
p.IsFavorite, err = isFavorite(s, p.ID, a, FavoriteKindProject)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.Subscription, err = GetSubscription(s, SubscriptionEntityProject, l.ID, a)
|
||||
p.Subscription, err = GetSubscription(s, SubscriptionEntityProject, p.ID, a)
|
||||
return
|
||||
}
|
||||
|
||||
@ -345,62 +359,32 @@ func GetProjectsByIDs(s *xorm.Session, projectIDs []int64) (projects map[int64]*
|
||||
}
|
||||
|
||||
type projectOptions struct {
|
||||
search string
|
||||
user *user.User
|
||||
page int
|
||||
perPage int
|
||||
isArchived bool
|
||||
search string
|
||||
user *user.User
|
||||
page int
|
||||
perPage int
|
||||
getArchived bool
|
||||
}
|
||||
|
||||
func getUserProjectsStatement(userID int64) *builder.Builder {
|
||||
func getUserProjectsStatement(userID int64, search string, getArchived bool) *builder.Builder {
|
||||
dialect := config.DatabaseType.GetString()
|
||||
if dialect == "sqlite" {
|
||||
dialect = builder.SQLITE
|
||||
}
|
||||
|
||||
return builder.Dialect(dialect).
|
||||
Select("l.*").
|
||||
From("projects", "l").
|
||||
Join("INNER", "namespaces n", "l.namespace_id = n.id").
|
||||
Join("LEFT", "team_namespaces tn", "tn.namespace_id = n.id").
|
||||
Join("LEFT", "team_members tm", "tm.team_id = tn.team_id").
|
||||
Join("LEFT", "team_projects tl", "l.id = tl.project_id").
|
||||
Join("LEFT", "team_members tm2", "tm2.team_id = tl.team_id").
|
||||
Join("LEFT", "users_projects ul", "ul.project_id = l.id").
|
||||
Join("LEFT", "users_namespaces un", "un.namespace_id = l.namespace_id").
|
||||
Where(builder.Or(
|
||||
builder.Eq{"tm.user_id": userID},
|
||||
builder.Eq{"tm2.user_id": userID},
|
||||
builder.Eq{"ul.user_id": userID},
|
||||
builder.Eq{"un.user_id": userID},
|
||||
builder.Eq{"l.owner_id": userID},
|
||||
)).
|
||||
OrderBy("position").
|
||||
GroupBy("l.id")
|
||||
}
|
||||
|
||||
// Gets the projects only, without any tasks or so
|
||||
func getRawProjectsForUser(s *xorm.Session, opts *projectOptions) (projects []*Project, resultCount int, totalItems int64, err error) {
|
||||
fullUser, err := user.GetUserByID(s, opts.user.ID)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
// Adding a 1=1 condition by default here because xorm always needs a condition and cannot handle nil conditions
|
||||
var isArchivedCond builder.Cond = builder.Eq{"1": 1}
|
||||
if !opts.isArchived {
|
||||
isArchivedCond = builder.And(
|
||||
var getArchivedCond builder.Cond = builder.Eq{"1": 1}
|
||||
if !getArchived {
|
||||
getArchivedCond = builder.And(
|
||||
builder.Eq{"l.is_archived": false},
|
||||
builder.Eq{"n.is_archived": false},
|
||||
)
|
||||
}
|
||||
|
||||
limit, start := getLimitFromPageIndex(opts.page, opts.perPage)
|
||||
|
||||
var filterCond builder.Cond
|
||||
ids := []int64{}
|
||||
if opts.search != "" {
|
||||
vals := strings.Split(opts.search, ",")
|
||||
if search != "" {
|
||||
vals := strings.Split(search, ",")
|
||||
for _, val := range vals {
|
||||
v, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
@ -411,65 +395,114 @@ func getRawProjectsForUser(s *xorm.Session, opts *projectOptions) (projects []*P
|
||||
}
|
||||
}
|
||||
|
||||
filterCond = db.ILIKE("l.title", opts.search)
|
||||
filterCond = db.ILIKE("l.title", search)
|
||||
if len(ids) > 0 {
|
||||
filterCond = builder.In("l.id", ids)
|
||||
}
|
||||
|
||||
// Gets all Projects where the user is either owner or in a team which has access to the project
|
||||
// Or in a team which has namespace read access
|
||||
return builder.Dialect(dialect).
|
||||
Select("l.*").
|
||||
From("projects", "l").
|
||||
// TODO: remove namespaces
|
||||
Join("INNER", "namespaces n", "l.namespace_id = n.id").
|
||||
Join("LEFT", "team_namespaces tn", "tn.namespace_id = n.id").
|
||||
Join("LEFT", "team_members tm", "tm.team_id = tn.team_id").
|
||||
Join("LEFT", "team_projects tl", "l.id = tl.project_id").
|
||||
Join("LEFT", "team_members tm2", "tm2.team_id = tl.team_id").
|
||||
Join("LEFT", "users_projects ul", "ul.project_id = l.id").
|
||||
Join("LEFT", "users_namespaces un", "un.namespace_id = l.namespace_id").
|
||||
Where(builder.And(
|
||||
builder.Or(
|
||||
builder.Eq{"tm.user_id": userID},
|
||||
builder.Eq{"tm2.user_id": userID},
|
||||
builder.Eq{"ul.user_id": userID},
|
||||
builder.Eq{"un.user_id": userID},
|
||||
builder.Eq{"l.owner_id": userID},
|
||||
),
|
||||
filterCond,
|
||||
getArchivedCond,
|
||||
)).
|
||||
OrderBy("position").
|
||||
GroupBy("l.id")
|
||||
}
|
||||
|
||||
query := getUserProjectsStatement(fullUser.ID).
|
||||
Where(filterCond).
|
||||
Where(isArchivedCond)
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit, start)
|
||||
}
|
||||
err = s.SQL(query).Find(&projects)
|
||||
// Gets the projects with their children without any tasks
|
||||
func getRawProjectsForUser(s *xorm.Session, opts *projectOptions) (projects map[int64]*Project, resultCount int, totalItems int64, err error) {
|
||||
fullUser, err := user.GetUserByID(s, opts.user.ID)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
query = getUserProjectsStatement(fullUser.ID).
|
||||
Where(filterCond).
|
||||
Where(isArchivedCond)
|
||||
limit, start := getLimitFromPageIndex(opts.page, opts.perPage)
|
||||
|
||||
// Gets all projects where the user is either owner or it was shared to them
|
||||
|
||||
allProjects := make(map[int64]*Project)
|
||||
|
||||
query := getUserProjectsStatement(fullUser.ID, opts.search, opts.getArchived)
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit, start)
|
||||
}
|
||||
err = s.SQL(query).Find(&allProjects)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
query = getUserProjectsStatement(fullUser.ID, opts.search, opts.getArchived)
|
||||
totalItems, err = s.
|
||||
SQL(query.Select("count(*)")).
|
||||
Count(&Project{})
|
||||
return projects, len(projects), totalItems, err
|
||||
|
||||
if len(allProjects) == 0 {
|
||||
return nil, 0, totalItems, nil
|
||||
}
|
||||
|
||||
return projects, len(allProjects), totalItems, err
|
||||
}
|
||||
|
||||
func getSavedFilterProjects(s *xorm.Session, doer *user.User) (savedFiltersNamespace *Project, err error) {
|
||||
savedFilters, err := getSavedFiltersForUser(s, doer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(savedFilters) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
savedFiltersPseudoNamespace := SavedFiltersPseudoProject
|
||||
savedFiltersPseudoNamespace.OwnerID = doer.ID
|
||||
*savedFiltersNamespace = *savedFiltersPseudoNamespace
|
||||
savedFiltersNamespace.ChildProjects = make([]*Project, 0, len(savedFilters))
|
||||
|
||||
for _, filter := range savedFilters {
|
||||
filterProject := filter.toProject()
|
||||
filterProject.ParentProjectID = savedFiltersNamespace.ID
|
||||
filterProject.Owner = doer
|
||||
savedFiltersNamespace.ChildProjects = append(savedFiltersNamespace.ChildProjects, filterProject)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// addProjectDetails adds owner user objects and project tasks to all projects in the slice
|
||||
func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err error) {
|
||||
func addProjectDetails(s *xorm.Session, projects map[int64]*Project, a web.Auth) (err error) {
|
||||
if len(projects) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var ownerIDs []int64
|
||||
for _, l := range projects {
|
||||
ownerIDs = append(ownerIDs, l.OwnerID)
|
||||
}
|
||||
|
||||
// Get all project owners
|
||||
owners := map[int64]*user.User{}
|
||||
if len(ownerIDs) > 0 {
|
||||
err = s.In("id", ownerIDs).Find(&owners)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var fileIDs []int64
|
||||
var projectIDs []int64
|
||||
for _, l := range projects {
|
||||
projectIDs = append(projectIDs, l.ID)
|
||||
if o, exists := owners[l.OwnerID]; exists {
|
||||
l.Owner = o
|
||||
}
|
||||
if l.BackgroundFileID != 0 {
|
||||
l.BackgroundInformation = &ProjectBackgroundType{Type: ProjectBackgroundUpload}
|
||||
}
|
||||
fileIDs = append(fileIDs, l.BackgroundFileID)
|
||||
var fileIDs []int64
|
||||
for _, p := range projects {
|
||||
ownerIDs = append(ownerIDs, p.OwnerID)
|
||||
projectIDs = append(projectIDs, p.ID)
|
||||
fileIDs = append(fileIDs, p.BackgroundFileID)
|
||||
}
|
||||
|
||||
owners, err := user.GetUsersByIDs(s, ownerIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
favs, err := getFavorites(s, projectIDs, a, FavoriteKindProject)
|
||||
@ -479,19 +512,26 @@ func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err er
|
||||
|
||||
subscriptions, err := GetSubscriptions(s, SubscriptionEntityProject, projectIDs, a)
|
||||
if err != nil {
|
||||
log.Errorf("An error occurred while getting project subscriptions for a namespace item: %s", err.Error())
|
||||
log.Errorf("An error occurred while getting project subscriptions for a project: %s", err.Error())
|
||||
subscriptions = make(map[int64]*Subscription)
|
||||
}
|
||||
|
||||
for _, project := range projects {
|
||||
for _, p := range projects {
|
||||
if o, exists := owners[p.OwnerID]; exists {
|
||||
p.Owner = o
|
||||
}
|
||||
if p.BackgroundFileID != 0 {
|
||||
p.BackgroundInformation = &ProjectBackgroundType{Type: ProjectBackgroundUpload}
|
||||
}
|
||||
|
||||
// Don't override the favorite state if it was already set from before (favorite saved filters do this)
|
||||
if project.IsFavorite {
|
||||
if p.IsFavorite {
|
||||
continue
|
||||
}
|
||||
project.IsFavorite = favs[project.ID]
|
||||
p.IsFavorite = favs[p.ID]
|
||||
|
||||
if subscription, exists := subscriptions[project.ID]; exists {
|
||||
project.Subscription = subscription
|
||||
if subscription, exists := subscriptions[p.ID]; exists {
|
||||
p.Subscription = subscription
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,46 +561,36 @@ func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err er
|
||||
return
|
||||
}
|
||||
|
||||
// NamespaceProject is a meta type to be able to join a project with its namespace
|
||||
type NamespaceProject struct {
|
||||
Project Project `xorm:"extends"`
|
||||
Namespace Namespace `xorm:"extends"`
|
||||
}
|
||||
|
||||
// CheckIsArchived returns an ErrProjectIsArchived or ErrNamespaceIsArchived if the project or its namespace is archived.
|
||||
func (l *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||
// When creating a new project, we check if the namespace is archived
|
||||
if l.ID == 0 {
|
||||
n := &Namespace{ID: l.NamespaceID}
|
||||
return n.CheckIsArchived(s)
|
||||
// CheckIsArchived returns an ErrProjectIsArchived or ErrNamespaceIsArchived if the project or any of its parent projects is archived.
|
||||
func (p *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||
// When creating a new project, we check if the parent is archived
|
||||
if p.ID == 0 {
|
||||
p := &Project{ID: p.ParentProjectID}
|
||||
return p.CheckIsArchived(s)
|
||||
}
|
||||
|
||||
nl := &NamespaceProject{}
|
||||
exists, err := s.
|
||||
Table("projects").
|
||||
Join("LEFT", "namespaces", "projects.namespace_id = namespaces.id").
|
||||
Where("projects.id = ? AND (projects.is_archived = true OR namespaces.is_archived = true)", l.ID).
|
||||
Get(nl)
|
||||
p, err := GetProjectSimpleByID(s, p.ID)
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
if exists && nl.Project.ID != 0 && nl.Project.IsArchived {
|
||||
return ErrProjectIsArchived{ProjectID: l.ID}
|
||||
}
|
||||
if exists && nl.Namespace.ID != 0 && nl.Namespace.IsArchived {
|
||||
return ErrNamespaceIsArchived{NamespaceID: nl.Namespace.ID}
|
||||
|
||||
// TODO: parent project
|
||||
|
||||
if p.IsArchived {
|
||||
return ErrProjectIsArchived{ProjectID: p.ID}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) error {
|
||||
if project.NamespaceID < 0 {
|
||||
return &ErrProjectCannotBelongToAPseudoNamespace{ProjectID: project.ID, NamespaceID: project.NamespaceID}
|
||||
if project.ParentProjectID < 0 {
|
||||
return &ErrProjectCannotBelongToAPseudoNamespace{ProjectID: project.ID, NamespaceID: project.ParentProjectID}
|
||||
}
|
||||
|
||||
// Check if the namespace exists
|
||||
if project.NamespaceID > 0 {
|
||||
_, err := GetNamespaceByID(s, project.NamespaceID)
|
||||
// Check if the parent project exists
|
||||
if project.ParentProjectID > 0 {
|
||||
_, err := GetProjectSimpleByID(s, project.ParentProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -635,19 +665,21 @@ func CreateProject(s *xorm.Session, project *Project, auth web.Auth) (err error)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateNewProjectForUser creates a new inbox project for a user. To prevent import cycles, we can't do that
|
||||
// directly in the user.Create function.
|
||||
func CreateNewProjectForUser(s *xorm.Session, user *user.User) (err error) {
|
||||
p := &Project{
|
||||
Title: "Inbox",
|
||||
}
|
||||
return p.Create(s, user)
|
||||
}
|
||||
|
||||
func UpdateProject(s *xorm.Session, project *Project, auth web.Auth, updateProjectBackground bool) (err error) {
|
||||
err = checkProjectBeforeUpdateOrDelete(s, project)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if project.NamespaceID == 0 {
|
||||
return &ErrProjectMustBelongToANamespace{
|
||||
ProjectID: project.ID,
|
||||
NamespaceID: project.NamespaceID,
|
||||
}
|
||||
}
|
||||
|
||||
// We need to specify the cols we want to update here to be able to un-archive projects
|
||||
colsToUpdate := []string{
|
||||
"title",
|
||||
@ -721,27 +753,27 @@ func UpdateProject(s *xorm.Session, project *Project, auth web.Auth, updateProje
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects/{id} [post]
|
||||
func (l *Project) Update(s *xorm.Session, a web.Auth) (err error) {
|
||||
fid := getSavedFilterIDFromProjectID(l.ID)
|
||||
func (p *Project) Update(s *xorm.Session, a web.Auth) (err error) {
|
||||
fid := getSavedFilterIDFromProjectID(p.ID)
|
||||
if fid > 0 {
|
||||
f, err := getSavedFilterSimpleByID(s, fid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Title = l.Title
|
||||
f.Description = l.Description
|
||||
f.IsFavorite = l.IsFavorite
|
||||
f.Title = p.Title
|
||||
f.Description = p.Description
|
||||
f.IsFavorite = p.IsFavorite
|
||||
err = f.Update(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*l = *f.toProject()
|
||||
*p = *f.toProject()
|
||||
return nil
|
||||
}
|
||||
|
||||
return UpdateProject(s, l, a, false)
|
||||
return UpdateProject(s, p, a, false)
|
||||
}
|
||||
|
||||
func updateProjectLastUpdated(s *xorm.Session, project *Project) error {
|
||||
@ -773,13 +805,13 @@ func updateProjectByTaskID(s *xorm.Session, taskID int64) (err error) {
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /namespaces/{namespaceID}/projects [put]
|
||||
func (l *Project) Create(s *xorm.Session, a web.Auth) (err error) {
|
||||
err = CreateProject(s, l, a)
|
||||
func (p *Project) Create(s *xorm.Session, a web.Auth) (err error) {
|
||||
err = CreateProject(s, p, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return l.ReadOne(s, a)
|
||||
return p.ReadOne(s, a)
|
||||
}
|
||||
|
||||
// Delete implements the delete method of CRUDable
|
||||
@ -794,7 +826,7 @@ func (l *Project) Create(s *xorm.Session, a web.Auth) (err error) {
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects/{id} [delete]
|
||||
func (l *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
func (p *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
|
||||
fullList, err := GetProjectSimpleByID(s, l.ID)
|
||||
if err != nil {
|
||||
@ -802,14 +834,14 @@ func (l *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
}
|
||||
|
||||
// Delete the project
|
||||
_, err = s.ID(l.ID).Delete(&Project{})
|
||||
_, err = s.ID(p.ID).Delete(&Project{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete all tasks on that project
|
||||
// Using the loop to make sure all related entities to all tasks are properly deleted as well.
|
||||
tasks, _, _, err := getRawTasksForProjects(s, []*Project{l}, a, &taskOptions{})
|
||||
tasks, _, _, err := getRawTasksForProjects(s, []*Project{p}, a, &taskOptions{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -827,7 +859,7 @@ func (l *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
}
|
||||
|
||||
return events.Dispatch(&ProjectDeletedEvent{
|
||||
Project: l,
|
||||
Project: p,
|
||||
Doer: a,
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user