mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
feat: add private project access
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
df05b1a968
commit
c761282d76
29 files changed
+1197
-29
No files matched your search
@@ -119,6 +119,7 @@ import { finalizeSummarySlotsForTerminalIssue } from "./summary-slot-finalizatio
|
||||
import { logActivity } from "./activity-log.js";
|
||||
import { invalidateIssuePrivacyGrantCache } from "./authorization.js";
|
||||
import { buildIssueChanges } from "./issue-change-receipt.js";
|
||||
import { ensurePersonalPrivateProject } from "./projects.js";
|
||||
|
||||
const ALL_ISSUE_STATUSES = ["backlog", "todo", "in_progress", "in_review", "blocked", "done", "cancelled"];
|
||||
const MAX_ISSUE_COMMENT_PAGE_LIMIT = 500;
|
||||
@@ -6659,6 +6660,7 @@ export function issueService(db: Db) {
|
||||
|
||||
let visibility = issueData.visibility ?? "open";
|
||||
let privacyRootIssueId: string | null = null;
|
||||
let inheritedPrivateSubtree = false;
|
||||
if (issueData.parentId) {
|
||||
const parentPrivacy = await tx
|
||||
.select({
|
||||
@@ -6671,6 +6673,7 @@ export function issueService(db: Db) {
|
||||
if (parentPrivacy?.visibility === "private") {
|
||||
visibility = "private";
|
||||
privacyRootIssueId = parentPrivacy.privacyRootIssueId ?? issueData.parentId;
|
||||
inheritedPrivateSubtree = true;
|
||||
}
|
||||
}
|
||||
if (visibility === "private" && !privacyRootIssueId) {
|
||||
@@ -6678,6 +6681,30 @@ export function issueService(db: Db) {
|
||||
privacyRootIssueId = issueData.id;
|
||||
}
|
||||
|
||||
if (issueData.projectId) {
|
||||
const selectedProject = await tx
|
||||
.select({ visibility: projects.visibility })
|
||||
.from(projects)
|
||||
.where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
if (!selectedProject) throw notFound("Project not found");
|
||||
if (selectedProject.visibility === "private") {
|
||||
visibility = "private";
|
||||
if (!privacyRootIssueId) {
|
||||
issueData.id ??= randomUUID();
|
||||
privacyRootIssueId = issueData.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visibility === "private" && !issueData.projectId && !inheritedPrivateSubtree) {
|
||||
if (!responsibleUserId) {
|
||||
throw unprocessable("Private tasks require a responsible user");
|
||||
}
|
||||
const personalProject = await ensurePersonalPrivateProject(tx, companyId, responsibleUserId);
|
||||
issueData.projectId = personalProject.id;
|
||||
}
|
||||
|
||||
const values = {
|
||||
...issueData,
|
||||
visibility,
|
||||
@@ -6719,6 +6746,15 @@ export function issueService(db: Db) {
|
||||
);
|
||||
|
||||
const [issue] = await tx.insert(issues).values(values).returning();
|
||||
if (issue.visibility === "private" && !inheritedPrivateSubtree && issueData.createdByAgentId) {
|
||||
await tx.insert(issueAccessGrants).values({
|
||||
issueId: issue.privacyRootIssueId ?? issue.id,
|
||||
subjectType: "agent",
|
||||
subjectId: issueData.createdByAgentId,
|
||||
source: "explicit",
|
||||
grantedByAgentId: issueData.createdByAgentId,
|
||||
}).onConflictDoNothing();
|
||||
}
|
||||
await ensureAssignmentIssueAccessGrant(tx, issue, null, {
|
||||
agentId: issueData.createdByAgentId ?? null,
|
||||
userId: issueData.createdByUserId ?? null,
|
||||
@@ -7045,6 +7081,18 @@ export function issueService(db: Db) {
|
||||
} else if (issueData.visibility === "open") {
|
||||
patch.privacyRootIssueId = null;
|
||||
}
|
||||
if (issueData.projectId) {
|
||||
const selectedProject = await dbOrTx
|
||||
.select({ visibility: projects.visibility })
|
||||
.from(projects)
|
||||
.where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, existing.companyId)))
|
||||
.then((rows: Array<{ visibility: string }>) => rows[0] ?? null);
|
||||
if (!selectedProject) throw notFound("Project not found");
|
||||
if (selectedProject.visibility === "private") {
|
||||
patch.visibility = "private";
|
||||
patch.privacyRootIssueId = existing.privacyRootIssueId ?? existing.id;
|
||||
}
|
||||
}
|
||||
if (existing.status !== "blocked" && issueData.status === "blocked") {
|
||||
patch.blockedTransitionAt = patch.updatedAt;
|
||||
patch.blockedOwnerNotifiedAt = null;
|
||||
|
||||
Reference in new issue
Block a user