fix: exclude soft-deleted projects from all raw SQL queries

Add deleted_at IS NULL filters to:
- getUserProjectsStatement (project listing base query)
- getAllProjectsForUser recursive CTE
- GetAllParentProjects recursive CTE
- setArchiveStateForProjectDescendants CTE
- checkPermissionsForProjects permission resolver CTE
- Task overdue reminders JOIN
- Subscription CTEs (project and task)
- Task search parent_project sub-table filters
- ListUsersFromProject query
- RepairOrphanedProjects (exclude soft-deleted from orphan detection)
This commit is contained in:
kolaente 2026-03-26 16:14:35 +01:00
parent 6c2e2cda4f
commit 56f42a293c
6 changed files with 14 additions and 8 deletions

View File

@ -261,7 +261,7 @@ WITH RECURSIVE
0 AS level,
id AS original_project_id
FROM projects
WHERE id IN (`+utils.JoinInt64Slice(projectIDs, ", ")+`)
WHERE id IN (`+utils.JoinInt64Slice(projectIDs, ", ")+`) AND deleted_at IS NULL
UNION ALL
@ -271,7 +271,8 @@ WITH RECURSIVE
ph.level + 1,
ph.original_project_id
FROM projects p
INNER JOIN project_hierarchy ph ON p.id = ph.parent_project_id),
INNER JOIN project_hierarchy ph ON p.id = ph.parent_project_id
WHERE p.deleted_at IS NULL),
-- Calculate max team permission for each project/user combination
max_team_permissions AS (

View File

@ -36,9 +36,11 @@ func RepairOrphanedProjects(s *xorm.Session, dryRun bool) (*RepairOrphanedProjec
result := &RepairOrphanedProjectsResult{}
var orphans []*Project
// Use raw SQL that includes soft-deleted parents to avoid false positives:
// a child of a soft-deleted parent is NOT orphaned.
err := s.SQL(`SELECT p.* FROM projects p
LEFT JOIN projects parent ON p.parent_project_id = parent.id
WHERE p.parent_project_id > 0 AND parent.id IS NULL`).
WHERE p.parent_project_id > 0 AND parent.id IS NULL AND p.deleted_at IS NULL`).
Find(&orphans)
if err != nil {
return nil, err

View File

@ -252,7 +252,7 @@ WITH RECURSIVE project_hierarchy AS (
0 AS level,
id AS original_project_id
FROM projects
WHERE id IN (`+entityIDString+`)
WHERE id IN (`+entityIDString+`) AND deleted_at IS NULL
UNION ALL
@ -264,6 +264,7 @@ WITH RECURSIVE project_hierarchy AS (
ph.original_project_id
FROM projects p
INNER JOIN project_hierarchy ph ON p.id = ph.parent_project_id
WHERE p.deleted_at IS NULL
),
subscription_hierarchy AS (
@ -318,7 +319,7 @@ WITH RECURSIVE project_hierarchy AS (
t.id AS task_id
FROM tasks t
JOIN projects p ON t.project_id = p.id
WHERE t.id IN (`+entityIDString+`)
WHERE t.id IN (`+entityIDString+`) AND p.deleted_at IS NULL
UNION ALL
@ -330,6 +331,7 @@ WITH RECURSIVE project_hierarchy AS (
ph.task_id
FROM projects p
INNER JOIN project_hierarchy ph ON p.id = ph.parent_project_id
WHERE p.deleted_at IS NULL
),
subscription_hierarchy AS (

View File

@ -38,7 +38,7 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time, cond builder.Cond) (u
var tasks []*Task
err = s.
Where("due_date is not null AND due_date < ? AND projects.is_archived = false", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
Where("due_date is not null AND due_date < ? AND projects.is_archived = false AND projects.deleted_at IS NULL", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
Join("LEFT", "projects", "projects.id = tasks.project_id").
And("done = false").
Find(&tasks)

View File

@ -64,13 +64,13 @@ var subTableFilters = SubTableFilters{
},
"parent_project": {
Table: "projects",
BaseFilter: "tasks.project_id = id",
BaseFilter: "tasks.project_id = id AND deleted_at IS NULL",
FilterableField: "parent_project_id",
AllowNullCheck: false,
},
"parent_project_id": {
Table: "projects",
BaseFilter: "tasks.project_id = id",
BaseFilter: "tasks.project_id = id AND deleted_at IS NULL",
FilterableField: "parent_project_id",
AllowNullCheck: false,
},

View File

@ -66,6 +66,7 @@ func ListUsersFromProject(s *xorm.Session, l *Project, currentUser *user.User, s
builder.Or(builder.Eq{"tl.permission": PermissionAdmin}),
),
builder.Eq{"l.id": currentProject.ID},
builder.IsNull{"l.deleted_at"},
).
Find(&currentUserIDs)
if err != nil {