mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
Update private task storage compatibility
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
19879cc29b
commit
a34b90c8f1
2 files changed
-144
No files matched your search
@@ -204,33 +204,6 @@ BEGIN LOOP
|
||||
cursor_id := next_id;
|
||||
END LOOP; END $$;
|
||||
|
||||
--> statement-breakpoint
|
||||
-- Bind historical inline draft images to their first owned task. Unbound drafts
|
||||
-- remain uploader-only. Attachment deletion never makes the draft company-open.
|
||||
DO $$
|
||||
DECLARE cursor_id uuid := '00000000-0000-0000-0000-000000000000'; next_id uuid;
|
||||
BEGIN
|
||||
LOOP
|
||||
SELECT max(id::text)::uuid INTO next_id FROM (
|
||||
SELECT id FROM assets WHERE id > cursor_id AND object_key LIKE company_id::text || '/assets/issues/drafts/%'
|
||||
ORDER BY id LIMIT 1000
|
||||
) batch;
|
||||
EXIT WHEN next_id IS NULL;
|
||||
INSERT INTO issue_attachments (company_id, issue_id, asset_id)
|
||||
SELECT a.company_id, first_issue.id, a.id FROM assets a
|
||||
CROSS JOIN LATERAL (
|
||||
SELECT i.id FROM issues i WHERE i.company_id = a.company_id
|
||||
AND (i.created_by_user_id = a.created_by_user_id OR i.responsible_user_id = a.created_by_user_id
|
||||
OR i.created_by_agent_id = a.created_by_agent_id)
|
||||
AND strpos(coalesce(i.description, ''), '/api/assets/' || a.id::text || '/content') > 0
|
||||
ORDER BY i.created_at, i.id LIMIT 1
|
||||
) first_issue
|
||||
WHERE a.id > cursor_id AND a.id <= next_id AND a.object_key LIKE a.company_id::text || '/assets/issues/drafts/%'
|
||||
ON CONFLICT DO NOTHING;
|
||||
cursor_id := next_id;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
--> statement-breakpoint
|
||||
-- Preserve operation history while retaining every original task/run boundary.
|
||||
CREATE OR REPLACE FUNCTION paperclip_keep_operation_privacy_sources() RETURNS trigger AS $$
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
issueComments,
|
||||
issueInboxArchives,
|
||||
issueDocuments,
|
||||
issueAccessGrants,
|
||||
issuePlanDecompositions,
|
||||
issueReadStates,
|
||||
issueRelations,
|
||||
@@ -26,7 +25,6 @@ import {
|
||||
issueWorkProducts,
|
||||
issues,
|
||||
projectWorkspaces,
|
||||
projectAccessMembers,
|
||||
projects,
|
||||
workspaceOperations,
|
||||
toolApplications,
|
||||
@@ -3282,121 +3280,6 @@ describeEmbeddedPostgres("issueService.create workspace inheritance", () => {
|
||||
expect(child.responsibleUserId).toBe(responsibleUserId);
|
||||
});
|
||||
|
||||
it("creates children under a private issue in the same private subtree", async () => {
|
||||
const companyId = randomUUID();
|
||||
const parentIssueId = randomUUID();
|
||||
await db.insert(companies).values({
|
||||
id: companyId,
|
||||
name: "Private subtree company",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
});
|
||||
await db.insert(issues).values({
|
||||
id: parentIssueId,
|
||||
companyId,
|
||||
title: "Private root",
|
||||
visibility: "private",
|
||||
privacyRootIssueId: parentIssueId,
|
||||
});
|
||||
|
||||
const child = await svc.create(companyId, {
|
||||
parentId: parentIssueId,
|
||||
title: "Inherited private child",
|
||||
visibility: "open",
|
||||
});
|
||||
|
||||
expect(child.visibility).toBe("private");
|
||||
expect(child.privacyRootIssueId).toBe(parentIssueId);
|
||||
});
|
||||
|
||||
it("creates one personal private project and parks private tasks there", async () => {
|
||||
const companyId = randomUUID();
|
||||
const responsibleUserId = randomUUID();
|
||||
await db.insert(companies).values({
|
||||
id: companyId,
|
||||
name: "Personal private tasks company",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
});
|
||||
|
||||
const [first, second] = await Promise.all([
|
||||
svc.create(companyId, {
|
||||
title: "First personal private task",
|
||||
visibility: "private",
|
||||
createdByUserId: responsibleUserId,
|
||||
}),
|
||||
svc.create(companyId, {
|
||||
title: "Second personal private task",
|
||||
visibility: "private",
|
||||
createdByUserId: responsibleUserId,
|
||||
}),
|
||||
]);
|
||||
|
||||
const personalProjects = await db.select().from(projects)
|
||||
.where(eq(projects.personalOwnerUserId, responsibleUserId));
|
||||
expect(personalProjects).toHaveLength(1);
|
||||
expect(personalProjects[0]).toMatchObject({
|
||||
companyId,
|
||||
name: "My private tasks",
|
||||
visibility: "private",
|
||||
});
|
||||
expect(first.projectId).toBe(personalProjects[0]!.id);
|
||||
expect(second.projectId).toBe(personalProjects[0]!.id);
|
||||
expect(first.privacyRootIssueId).toBe(first.id);
|
||||
expect(second.privacyRootIssueId).toBe(second.id);
|
||||
await expect(db.select().from(projectAccessMembers)
|
||||
.where(eq(projectAccessMembers.projectId, personalProjects[0]!.id)))
|
||||
.resolves.toEqual([
|
||||
expect.objectContaining({ subjectType: "user", subjectId: responsibleUserId }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps a responsible human and grants an agent access to its explicitly private task", async () => {
|
||||
const companyId = randomUUID();
|
||||
const responsibleUserId = randomUUID();
|
||||
const creatorAgentId = randomUUID();
|
||||
await db.insert(companies).values({
|
||||
id: companyId,
|
||||
name: "Agent private task company",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
});
|
||||
await db.insert(agents).values({
|
||||
id: creatorAgentId,
|
||||
companyId,
|
||||
name: "Private task creator",
|
||||
role: "engineer",
|
||||
adapterType: "process",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
});
|
||||
|
||||
const issue = await svc.create(companyId, {
|
||||
title: "Agent email scratch task",
|
||||
visibility: "private",
|
||||
createdByAgentId: creatorAgentId,
|
||||
actorResponsibleUserId: responsibleUserId,
|
||||
});
|
||||
|
||||
expect(issue.responsibleUserId).toBe(responsibleUserId);
|
||||
const personalProject = await db.select().from(projects)
|
||||
.where(eq(projects.id, issue.projectId!))
|
||||
.then((rows) => rows[0]!);
|
||||
expect(personalProject.personalOwnerUserId).toBe(responsibleUserId);
|
||||
await expect(db.select().from(projectAccessMembers)
|
||||
.where(eq(projectAccessMembers.projectId, personalProject.id)))
|
||||
.resolves.toEqual([
|
||||
expect.objectContaining({ subjectType: "user", subjectId: responsibleUserId }),
|
||||
]);
|
||||
await expect(db.select().from(issueAccessGrants)
|
||||
.where(eq(issueAccessGrants.issueId, issue.id)))
|
||||
.resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
subjectType: "agent",
|
||||
subjectId: creatorAgentId,
|
||||
source: "explicit",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("only honors explicit responsibleUserId for trusted issue create callers", async () => {
|
||||
const companyId = randomUUID();
|
||||
const creatorUserId = randomUUID();
|
||||
|
||||
Reference in new issue
Block a user