@plandesk/api 0.19.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-control.d.ts +150 -0
- package/dist/access-control.js +63 -0
- package/dist/agent-keys.d.ts +81 -0
- package/dist/agent-keys.js +181 -0
- package/dist/auth-context.d.ts +60 -0
- package/dist/auth-context.js +41 -0
- package/dist/auth.d.ts +33 -0
- package/dist/auth.js +357 -0
- package/dist/better-auth.d.ts +66 -0
- package/dist/better-auth.js +106 -0
- package/dist/github.d.ts +57 -0
- package/dist/github.js +115 -0
- package/dist/hosted-auth.d.ts +23 -0
- package/dist/hosted-auth.js +49 -0
- package/dist/identity.d.ts +38 -0
- package/dist/identity.js +163 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +12 -2
- package/dist/invitations.d.ts +94 -0
- package/dist/invitations.js +211 -0
- package/dist/organizations.d.ts +32 -0
- package/dist/organizations.js +63 -0
- package/dist/permissions.d.ts +19 -0
- package/dist/permissions.js +42 -0
- package/dist/projection.d.ts +1 -1
- package/dist/projection.js +5 -5
- package/dist/routes/agent-runs.js +3 -3
- package/dist/routes/artifacts.js +6 -6
- package/dist/routes/auth.d.ts +13 -0
- package/dist/routes/auth.js +83 -0
- package/dist/routes/canvas.js +5 -5
- package/dist/routes/comments.js +11 -11
- package/dist/routes/documents.js +10 -10
- package/dist/routes/folders.js +8 -8
- package/dist/routes/goals.js +11 -11
- package/dist/routes/notes.js +8 -8
- package/dist/routes/orgs.d.ts +17 -0
- package/dist/routes/orgs.js +241 -0
- package/dist/routes/projects.js +13 -13
- package/dist/routes/shares.js +124 -2
- package/dist/routes/submissions.js +7 -8
- package/dist/routes/tags.js +6 -6
- package/dist/routes/tasks.js +14 -3
- package/dist/routes/tokens.js +4 -4
- package/dist/serialize.d.ts +3 -3
- package/dist/server.d.ts +20 -2
- package/dist/server.js +57 -9
- package/dist/services/agent-runs.d.ts +13 -14
- package/dist/services/agent-runs.js +39 -41
- package/dist/services/artifacts.d.ts +6 -7
- package/dist/services/artifacts.js +30 -26
- package/dist/services/canvas.d.ts +11 -12
- package/dist/services/canvas.js +58 -41
- package/dist/services/comments.d.ts +11 -12
- package/dist/services/comments.js +44 -64
- package/dist/services/documents.d.ts +10 -11
- package/dist/services/documents.js +74 -55
- package/dist/services/files.d.ts +2 -1
- package/dist/services/files.js +13 -5
- package/dist/services/folders.d.ts +7 -8
- package/dist/services/folders.js +45 -33
- package/dist/services/goals.d.ts +24 -25
- package/dist/services/goals.js +60 -53
- package/dist/services/index.d.ts +2 -5
- package/dist/services/index.js +15 -20
- package/dist/services/notes.d.ts +7 -8
- package/dist/services/notes.js +35 -23
- package/dist/services/org-scope.d.ts +22 -0
- package/dist/services/org-scope.js +38 -0
- package/dist/services/projects.d.ts +12 -13
- package/dist/services/projects.js +90 -58
- package/dist/services/scope.d.ts +12 -0
- package/dist/services/scope.js +29 -0
- package/dist/services/share.d.ts +74 -10
- package/dist/services/share.js +213 -23
- package/dist/services/sync.d.ts +7 -29
- package/dist/services/sync.js +37 -142
- package/dist/services/tags.d.ts +6 -7
- package/dist/services/tags.js +34 -22
- package/dist/services/tasks.d.ts +24 -17
- package/dist/services/tasks.js +97 -58
- package/dist/services/tokens.d.ts +6 -5
- package/dist/services/tokens.js +13 -6
- package/dist/session.d.ts +5 -0
- package/dist/session.js +8 -0
- package/dist/storage/local.js +16 -10
- package/dist/storage/s3.js +11 -6
- package/dist/test-ba-org.d.ts +16 -0
- package/dist/test-ba-org.js +35 -0
- package/dist/test-helpers.d.ts +21 -5
- package/dist/test-helpers.js +45 -8
- package/dist/vercel.d.ts +3 -0
- package/dist/vercel.js +62 -0
- package/dist/worker.d.ts +44 -0
- package/dist/worker.js +101 -0
- package/package.json +8 -5
- package/web/assets/index-D29SyYFS.css +2 -0
- package/web/assets/index-tBJ_K3ur.js +358 -0
- package/web/index.html +2 -2
- package/web/assets/index-CZs-KaPG.css +0 -2
- package/web/assets/index-zUcwwGtr.js +0 -358
package/dist/services/tags.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type Db } from '@plandesk/db';
|
|
2
|
-
import type { EventBus } from '../events.js';
|
|
3
2
|
import { type SerializedTag } from '../serialize.js';
|
|
4
|
-
|
|
3
|
+
import { type OrgScopedDeps } from './org-scope.js';
|
|
4
|
+
export type TagServiceDeps = OrgScopedDeps & {
|
|
5
5
|
db: Db;
|
|
6
|
-
eventBus: EventBus;
|
|
7
6
|
};
|
|
8
7
|
export type CreateTagInput = {
|
|
9
8
|
name: string;
|
|
@@ -18,10 +17,10 @@ export declare class InvalidTagError extends Error {
|
|
|
18
17
|
}
|
|
19
18
|
export declare function normalizeTagName(name: string): string;
|
|
20
19
|
export declare function createTagService(deps: TagServiceDeps): {
|
|
21
|
-
list(projectId: string): SerializedTag[] | undefined
|
|
22
|
-
create(projectId: string, input: CreateTagInput): SerializedTag | undefined
|
|
23
|
-
update(id: string, input: UpdateTagInput): SerializedTag | undefined
|
|
24
|
-
delete(id: string): boolean
|
|
20
|
+
list(projectId: string): Promise<SerializedTag[] | undefined>;
|
|
21
|
+
create(projectId: string, input: CreateTagInput): Promise<SerializedTag | undefined>;
|
|
22
|
+
update(id: string, input: UpdateTagInput): Promise<SerializedTag | undefined>;
|
|
23
|
+
delete(id: string): Promise<boolean>;
|
|
25
24
|
};
|
|
26
25
|
export type TagService = ReturnType<typeof createTagService>;
|
|
27
26
|
//# sourceMappingURL=tags.d.ts.map
|
package/dist/services/tags.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createTag as dbCreateTag, deleteTag as dbDeleteTag, getProject, getTag as dbGetTag, getTagByName, listTags as dbListTags, updateTag as dbUpdateTag, } from '@plandesk/db';
|
|
2
2
|
import { serializeTag } from '../serialize.js';
|
|
3
|
+
import { assertPermission, resolveOrgId } from './org-scope.js';
|
|
4
|
+
import { assertProjectInOrg, ProjectNotInOrgError } from './scope.js';
|
|
3
5
|
export class InvalidTagError extends Error {
|
|
4
6
|
constructor(message) {
|
|
5
7
|
super(message);
|
|
@@ -14,64 +16,74 @@ export function normalizeTagName(name) {
|
|
|
14
16
|
return trimmed;
|
|
15
17
|
}
|
|
16
18
|
export function createTagService(deps) {
|
|
17
|
-
const { db
|
|
19
|
+
const { db } = deps;
|
|
18
20
|
return {
|
|
19
|
-
list(projectId) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
async list(projectId) {
|
|
22
|
+
try {
|
|
23
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
23
30
|
}
|
|
24
|
-
return dbListTags(db, projectId).map(serializeTag);
|
|
31
|
+
return (await dbListTags(db, projectId)).map(serializeTag);
|
|
25
32
|
},
|
|
26
|
-
create(projectId, input) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
async create(projectId, input) {
|
|
34
|
+
assertPermission(deps, 'task', 'update');
|
|
35
|
+
try {
|
|
36
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
30
43
|
}
|
|
31
44
|
const name = normalizeTagName(input.name);
|
|
32
|
-
if (getTagByName(db, projectId, name)) {
|
|
45
|
+
if (await getTagByName(db, projectId, name)) {
|
|
33
46
|
throw new InvalidTagError(`Tag already exists: ${name}`);
|
|
34
47
|
}
|
|
35
|
-
const tag = dbCreateTag(db, { projectId, name, color: input.color });
|
|
36
|
-
eventBus.emit({ type: 'tag_updated', projectId });
|
|
48
|
+
const tag = await dbCreateTag(db, { projectId, name, color: input.color });
|
|
37
49
|
return serializeTag(tag);
|
|
38
50
|
},
|
|
39
51
|
// Renaming propagates everywhere automatically: tasks reference the single
|
|
40
52
|
// tag row through the join table.
|
|
41
|
-
update(id, input) {
|
|
42
|
-
|
|
53
|
+
async update(id, input) {
|
|
54
|
+
assertPermission(deps, 'task', 'update');
|
|
55
|
+
const existing = await dbGetTag(db, id);
|
|
43
56
|
if (!existing) {
|
|
44
57
|
return undefined;
|
|
45
58
|
}
|
|
46
59
|
let name;
|
|
47
60
|
if (input.name !== undefined) {
|
|
48
61
|
name = normalizeTagName(input.name);
|
|
49
|
-
const conflict = getTagByName(db, existing.projectId, name);
|
|
62
|
+
const conflict = await getTagByName(db, existing.projectId, name);
|
|
50
63
|
if (conflict && conflict.id !== id) {
|
|
51
64
|
throw new InvalidTagError(`Tag already exists: ${name}`);
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
|
-
const tag = dbUpdateTag(db, id, {
|
|
67
|
+
const tag = await dbUpdateTag(db, id, {
|
|
55
68
|
...(name !== undefined ? { name } : {}),
|
|
56
69
|
...(input.color !== undefined ? { color: input.color } : {}),
|
|
57
70
|
});
|
|
58
71
|
if (!tag) {
|
|
59
72
|
return undefined;
|
|
60
73
|
}
|
|
61
|
-
eventBus.emit({ type: 'tag_updated', projectId: tag.projectId });
|
|
62
74
|
return serializeTag(tag);
|
|
63
75
|
},
|
|
64
76
|
// Deleting a tag removes it from all its tasks (cascade on the join table).
|
|
65
|
-
delete(id) {
|
|
66
|
-
|
|
77
|
+
async delete(id) {
|
|
78
|
+
assertPermission(deps, 'task', 'delete');
|
|
79
|
+
const existing = await dbGetTag(db, id);
|
|
67
80
|
if (!existing) {
|
|
68
81
|
return false;
|
|
69
82
|
}
|
|
70
|
-
const deleted = dbDeleteTag(db, id);
|
|
83
|
+
const deleted = await dbDeleteTag(db, id);
|
|
71
84
|
if (!deleted) {
|
|
72
85
|
return false;
|
|
73
86
|
}
|
|
74
|
-
eventBus.emit({ type: 'tag_updated', projectId: existing.projectId });
|
|
75
87
|
return true;
|
|
76
88
|
},
|
|
77
89
|
};
|
package/dist/services/tasks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Db, type TaskStatus } from '@plandesk/db';
|
|
2
|
-
import type { EventBus } from '../events.js';
|
|
3
2
|
import { serializeTask, type PaginationParams } from '../serialize.js';
|
|
3
|
+
import { type OrgScopedDeps } from './org-scope.js';
|
|
4
4
|
type SerializedTask = ReturnType<typeof serializeTask>;
|
|
5
5
|
export declare class InvalidGoalReferenceError extends Error {
|
|
6
6
|
constructor(goalId: string);
|
|
@@ -14,9 +14,15 @@ export type NextActionableResult = {
|
|
|
14
14
|
waiting_on: SerializedTask[];
|
|
15
15
|
}>;
|
|
16
16
|
};
|
|
17
|
-
export type
|
|
17
|
+
export type ClaimTaskResult = {
|
|
18
|
+
claimed: true;
|
|
19
|
+
task: SerializedTask;
|
|
20
|
+
} | {
|
|
21
|
+
claimed: false;
|
|
22
|
+
reason: 'taken_or_not_actionable';
|
|
23
|
+
};
|
|
24
|
+
export type TaskServiceDeps = OrgScopedDeps & {
|
|
18
25
|
db: Db;
|
|
19
|
-
eventBus: EventBus;
|
|
20
26
|
};
|
|
21
27
|
export type CreateTaskInput = {
|
|
22
28
|
label: string;
|
|
@@ -42,13 +48,13 @@ export type ListTasksFilter = {
|
|
|
42
48
|
tags?: string[];
|
|
43
49
|
};
|
|
44
50
|
export declare function createTaskService(deps: TaskServiceDeps): {
|
|
45
|
-
get(id: string): {
|
|
51
|
+
get(id: string): Promise<{
|
|
46
52
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
47
53
|
id: string;
|
|
48
54
|
project_id: string;
|
|
49
55
|
goal_id: string;
|
|
50
56
|
label: string;
|
|
51
|
-
status: "
|
|
57
|
+
status: "in_progress" | "done" | "todo" | "scope" | "backlog";
|
|
52
58
|
description: string | null;
|
|
53
59
|
x: number;
|
|
54
60
|
y: number;
|
|
@@ -56,14 +62,14 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
56
62
|
due_date: string | null;
|
|
57
63
|
created_at: string;
|
|
58
64
|
updated_at: string;
|
|
59
|
-
} | undefined
|
|
60
|
-
listByProject(projectId: string, filter?: ListTasksFilter, pagination?: PaginationParams): {
|
|
65
|
+
} | undefined>;
|
|
66
|
+
listByProject(projectId: string, filter?: ListTasksFilter, pagination?: PaginationParams): Promise<{
|
|
61
67
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
62
68
|
id: string;
|
|
63
69
|
project_id: string;
|
|
64
70
|
goal_id: string;
|
|
65
71
|
label: string;
|
|
66
|
-
status: "
|
|
72
|
+
status: "in_progress" | "done" | "todo" | "scope" | "backlog";
|
|
67
73
|
description: string | null;
|
|
68
74
|
x: number;
|
|
69
75
|
y: number;
|
|
@@ -71,14 +77,14 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
71
77
|
due_date: string | null;
|
|
72
78
|
created_at: string;
|
|
73
79
|
updated_at: string;
|
|
74
|
-
}[] | undefined
|
|
75
|
-
create(projectId: string, input: CreateTaskInput): {
|
|
80
|
+
}[] | undefined>;
|
|
81
|
+
create(projectId: string, input: CreateTaskInput): Promise<{
|
|
76
82
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
77
83
|
id: string;
|
|
78
84
|
project_id: string;
|
|
79
85
|
goal_id: string;
|
|
80
86
|
label: string;
|
|
81
|
-
status: "
|
|
87
|
+
status: "in_progress" | "done" | "todo" | "scope" | "backlog";
|
|
82
88
|
description: string | null;
|
|
83
89
|
x: number;
|
|
84
90
|
y: number;
|
|
@@ -86,14 +92,14 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
86
92
|
due_date: string | null;
|
|
87
93
|
created_at: string;
|
|
88
94
|
updated_at: string;
|
|
89
|
-
} | undefined
|
|
90
|
-
update(id: string, input: UpdateTaskInput): {
|
|
95
|
+
} | undefined>;
|
|
96
|
+
update(id: string, input: UpdateTaskInput): Promise<{
|
|
91
97
|
tags?: import("../serialize.js").SerializedTag[] | undefined;
|
|
92
98
|
id: string;
|
|
93
99
|
project_id: string;
|
|
94
100
|
goal_id: string;
|
|
95
101
|
label: string;
|
|
96
|
-
status: "
|
|
102
|
+
status: "in_progress" | "done" | "todo" | "scope" | "backlog";
|
|
97
103
|
description: string | null;
|
|
98
104
|
x: number;
|
|
99
105
|
y: number;
|
|
@@ -101,12 +107,13 @@ export declare function createTaskService(deps: TaskServiceDeps): {
|
|
|
101
107
|
due_date: string | null;
|
|
102
108
|
created_at: string;
|
|
103
109
|
updated_at: string;
|
|
104
|
-
} | undefined
|
|
105
|
-
delete(id: string): boolean
|
|
110
|
+
} | undefined>;
|
|
111
|
+
delete(id: string): Promise<boolean>;
|
|
112
|
+
claim(taskId: string, agentRef: string): Promise<ClaimTaskResult>;
|
|
106
113
|
nextActionable(projectId: string, filter?: {
|
|
107
114
|
goalId?: string;
|
|
108
115
|
tags?: string[];
|
|
109
|
-
}): NextActionableResult | undefined
|
|
116
|
+
}): Promise<NextActionableResult | undefined>;
|
|
110
117
|
};
|
|
111
118
|
export type TaskService = ReturnType<typeof createTaskService>;
|
|
112
119
|
export {};
|
package/dist/services/tasks.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { createTag, createTask, deleteCommentsByTarget, deleteEdgesByTaskId, deleteTask as dbDeleteTask, deleteTaskTagsByTaskId, getOrCreateDefaultGoal, getProject, listGoals, getTagByName, getTask, InvalidTaskStatusError, isTaskStatus, listEdges, listTagsByTaskForProject, listTagsForTask, listTasks, nullDocumentsLinkedTask, setTaskTags, taskIdsWithAnyTagName, updateTask, } from '@plandesk/db';
|
|
1
|
+
import { withTransaction, claimTask, createTag, createTask, deleteCommentsByTarget, deleteEdgesByTaskId, deleteTask as dbDeleteTask, deleteTaskTagsByTaskId, getOrCreateDefaultGoal, getProject, listGoals, getTagByName, getTask, InvalidTaskStatusError, isTaskStatus, listEdges, listTagsByTaskForProject, listTagsForTask, listTasks, nullDocumentsLinkedTask, setTaskTags, taskIdsWithAnyTagName, updateTask, } from '@plandesk/db';
|
|
2
2
|
import { serializeTask } from '../serialize.js';
|
|
3
|
+
import { assertPermission, resolveOrgId } from './org-scope.js';
|
|
4
|
+
import { assertProjectInOrg, ProjectNotInOrgError } from './scope.js';
|
|
3
5
|
import { normalizeTagName } from './tags.js';
|
|
4
6
|
export class InvalidGoalReferenceError extends Error {
|
|
5
7
|
constructor(goalId) {
|
|
@@ -18,7 +20,7 @@ function prerequisiteAndDependent(edge) {
|
|
|
18
20
|
return { prerequisite: edge.fromTaskId, dependent: edge.toTaskId };
|
|
19
21
|
}
|
|
20
22
|
// Resolves tag names to ids, auto-creating tags that do not exist yet.
|
|
21
|
-
function resolveTagIdsByName(db, projectId, names) {
|
|
23
|
+
async function resolveTagIdsByName(db, projectId, names) {
|
|
22
24
|
const seen = new Set();
|
|
23
25
|
const ids = [];
|
|
24
26
|
for (const raw of names) {
|
|
@@ -27,53 +29,65 @@ function resolveTagIdsByName(db, projectId, names) {
|
|
|
27
29
|
continue;
|
|
28
30
|
}
|
|
29
31
|
seen.add(name);
|
|
30
|
-
const
|
|
32
|
+
const existing = await getTagByName(db, projectId, name);
|
|
33
|
+
const tag = existing ?? (await createTag(db, { projectId, name }));
|
|
31
34
|
ids.push(tag.id);
|
|
32
35
|
}
|
|
33
36
|
return ids;
|
|
34
37
|
}
|
|
35
38
|
export function createTaskService(deps) {
|
|
36
|
-
const { db
|
|
39
|
+
const { db } = deps;
|
|
37
40
|
return {
|
|
38
|
-
get(id) {
|
|
39
|
-
const task = getTask(db, id);
|
|
41
|
+
async get(id) {
|
|
42
|
+
const task = await getTask(db, id);
|
|
40
43
|
if (!task) {
|
|
41
44
|
return undefined;
|
|
42
45
|
}
|
|
43
|
-
return serializeTask(task, listTagsForTask(db, id));
|
|
46
|
+
return serializeTask(task, await listTagsForTask(db, id));
|
|
44
47
|
},
|
|
45
|
-
listByProject(projectId, filter = {}, pagination = {}) {
|
|
48
|
+
async listByProject(projectId, filter = {}, pagination = {}) {
|
|
46
49
|
if (filter.status !== undefined && !isTaskStatus(filter.status)) {
|
|
47
50
|
throw new InvalidTaskStatusError(filter.status);
|
|
48
51
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
try {
|
|
53
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
52
60
|
}
|
|
53
61
|
const statusFilter = filter.status;
|
|
54
|
-
const tasks = listTasks(db, projectId, {
|
|
62
|
+
const tasks = await listTasks(db, projectId, {
|
|
55
63
|
...(statusFilter !== undefined ? { status: statusFilter } : {}),
|
|
56
64
|
...(filter.tags !== undefined ? { tagNames: filter.tags.map(normalizeTagName) } : {}),
|
|
57
65
|
...pagination,
|
|
58
66
|
});
|
|
59
|
-
const tagsByTask = listTagsByTaskForProject(db, projectId);
|
|
67
|
+
const tagsByTask = await listTagsByTaskForProject(db, projectId);
|
|
60
68
|
return tasks.map((task) => serializeTask(task, tagsByTask.get(task.id) ?? []));
|
|
61
69
|
},
|
|
62
|
-
create(projectId, input) {
|
|
70
|
+
async create(projectId, input) {
|
|
71
|
+
assertPermission(deps, 'task', 'create');
|
|
63
72
|
if (input.status !== undefined && !isTaskStatus(input.status)) {
|
|
64
73
|
throw new InvalidTaskStatusError(input.status);
|
|
65
74
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
try {
|
|
76
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
69
83
|
}
|
|
70
84
|
if (input.goalId !== undefined &&
|
|
71
|
-
!listGoals(db, projectId).some((g) => g.id === input.goalId)) {
|
|
85
|
+
!(await listGoals(db, projectId)).some((g) => g.id === input.goalId)) {
|
|
72
86
|
throw new InvalidGoalReferenceError(input.goalId);
|
|
73
87
|
}
|
|
74
|
-
const { task, tags } = db
|
|
75
|
-
const goalId = input.goalId ?? getOrCreateDefaultGoal(tx, projectId).id;
|
|
76
|
-
const row = createTask(tx, {
|
|
88
|
+
const { task, tags } = await withTransaction(db, async (tx) => {
|
|
89
|
+
const goalId = input.goalId ?? (await getOrCreateDefaultGoal(tx, projectId)).id;
|
|
90
|
+
const row = await createTask(tx, {
|
|
77
91
|
projectId,
|
|
78
92
|
goalId,
|
|
79
93
|
label: input.label,
|
|
@@ -85,73 +99,98 @@ export function createTaskService(deps) {
|
|
|
85
99
|
dueDate: input.dueDate,
|
|
86
100
|
});
|
|
87
101
|
if (input.tags !== undefined) {
|
|
88
|
-
setTaskTags(tx, row.id, resolveTagIdsByName(tx, projectId, input.tags));
|
|
102
|
+
await setTaskTags(tx, row.id, await resolveTagIdsByName(tx, projectId, input.tags));
|
|
89
103
|
}
|
|
90
|
-
return { task: row, tags: listTagsForTask(tx, row.id) };
|
|
91
|
-
});
|
|
92
|
-
eventBus.emit({
|
|
93
|
-
type: 'task_updated',
|
|
94
|
-
taskId: task.id,
|
|
95
|
-
projectId,
|
|
104
|
+
return { task: row, tags: await listTagsForTask(tx, row.id) };
|
|
96
105
|
});
|
|
97
106
|
return serializeTask(task, tags);
|
|
98
107
|
},
|
|
99
|
-
update(id, input) {
|
|
108
|
+
async update(id, input) {
|
|
109
|
+
assertPermission(deps, 'task', 'update');
|
|
100
110
|
if (input.status !== undefined && !isTaskStatus(input.status)) {
|
|
101
111
|
throw new InvalidTaskStatusError(input.status);
|
|
102
112
|
}
|
|
103
|
-
const existing = getTask(db, id);
|
|
113
|
+
const existing = await getTask(db, id);
|
|
104
114
|
if (!existing) {
|
|
105
115
|
return undefined;
|
|
106
116
|
}
|
|
117
|
+
try {
|
|
118
|
+
await assertProjectInOrg(db, existing.projectId, resolveOrgId(deps));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
107
126
|
const { tags: tagNames, ...columns } = input;
|
|
108
|
-
const result = db
|
|
109
|
-
const row = updateTask(tx, id, columns);
|
|
127
|
+
const result = await withTransaction(db, async (tx) => {
|
|
128
|
+
const row = await updateTask(tx, id, columns);
|
|
110
129
|
if (!row) {
|
|
111
130
|
return undefined;
|
|
112
131
|
}
|
|
113
132
|
if (tagNames !== undefined) {
|
|
114
|
-
setTaskTags(tx, id, resolveTagIdsByName(tx, existing.projectId, tagNames));
|
|
133
|
+
await setTaskTags(tx, id, await resolveTagIdsByName(tx, existing.projectId, tagNames));
|
|
115
134
|
}
|
|
116
|
-
return { task: row, tags: listTagsForTask(tx, id) };
|
|
135
|
+
return { task: row, tags: await listTagsForTask(tx, id) };
|
|
117
136
|
});
|
|
118
137
|
if (!result) {
|
|
119
138
|
return undefined;
|
|
120
139
|
}
|
|
121
|
-
eventBus.emit({
|
|
122
|
-
type: 'task_updated',
|
|
123
|
-
taskId: result.task.id,
|
|
124
|
-
projectId: result.task.projectId,
|
|
125
|
-
});
|
|
126
140
|
return serializeTask(result.task, result.tags);
|
|
127
141
|
},
|
|
128
|
-
delete(id) {
|
|
129
|
-
|
|
142
|
+
async delete(id) {
|
|
143
|
+
assertPermission(deps, 'task', 'delete');
|
|
144
|
+
const task = await getTask(db, id);
|
|
130
145
|
if (!task) {
|
|
131
146
|
return false;
|
|
132
147
|
}
|
|
148
|
+
try {
|
|
149
|
+
await assertProjectInOrg(db, task.projectId, resolveOrgId(deps));
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
133
157
|
const projectId = task.projectId;
|
|
134
|
-
db
|
|
135
|
-
deleteCommentsByTarget(tx, 'task', id);
|
|
136
|
-
deleteEdgesByTaskId(tx, id);
|
|
137
|
-
nullDocumentsLinkedTask(tx, id);
|
|
138
|
-
deleteTaskTagsByTaskId(tx, id);
|
|
139
|
-
dbDeleteTask(tx, id);
|
|
158
|
+
await withTransaction(db, async (tx) => {
|
|
159
|
+
await deleteCommentsByTarget(tx, 'task', id);
|
|
160
|
+
await deleteEdgesByTaskId(tx, id);
|
|
161
|
+
await nullDocumentsLinkedTask(tx, id);
|
|
162
|
+
await deleteTaskTagsByTaskId(tx, id);
|
|
163
|
+
await dbDeleteTask(tx, id);
|
|
140
164
|
});
|
|
141
|
-
eventBus.emit({ type: 'canvas_updated', projectId });
|
|
142
165
|
return true;
|
|
143
166
|
},
|
|
167
|
+
async claim(taskId, agentRef) {
|
|
168
|
+
assertPermission(deps, 'task', 'update');
|
|
169
|
+
const row = await claimTask(db, taskId, resolveOrgId(deps), agentRef);
|
|
170
|
+
if (!row) {
|
|
171
|
+
return { claimed: false, reason: 'taken_or_not_actionable' };
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
claimed: true,
|
|
175
|
+
task: serializeTask(row, await listTagsForTask(db, row.id)),
|
|
176
|
+
};
|
|
177
|
+
},
|
|
144
178
|
// filter.goalId scopes candidates to one goal; when omitted, the project's sole
|
|
145
179
|
// active goal is resolved. filter.tags (OR semantics) composes with goal scope;
|
|
146
180
|
// prerequisite completion is still evaluated against all tasks in the project.
|
|
147
|
-
nextActionable(projectId, filter = {}) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
181
|
+
async nextActionable(projectId, filter = {}) {
|
|
182
|
+
try {
|
|
183
|
+
await assertProjectInOrg(db, projectId, resolveOrgId(deps));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error instanceof ProjectNotInOrgError) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
151
190
|
}
|
|
152
191
|
let goalId = filter.goalId;
|
|
153
192
|
if (goalId === undefined) {
|
|
154
|
-
const active = listGoals(db, projectId).filter((goal) => goal.status === 'active');
|
|
193
|
+
const active = (await listGoals(db, projectId)).filter((goal) => goal.status === 'active');
|
|
155
194
|
if (active.length === 0) {
|
|
156
195
|
return { next_task: null, reason: 'no_active_goal', blocked: [] };
|
|
157
196
|
}
|
|
@@ -160,15 +199,15 @@ export function createTaskService(deps) {
|
|
|
160
199
|
}
|
|
161
200
|
goalId = active[0]?.id;
|
|
162
201
|
}
|
|
163
|
-
else if (!listGoals(db, projectId).some((goal) => goal.id === goalId)) {
|
|
202
|
+
else if (!(await listGoals(db, projectId)).some((goal) => goal.id === goalId)) {
|
|
164
203
|
return undefined;
|
|
165
204
|
}
|
|
166
|
-
const tasks = listTasks(db, projectId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
167
|
-
const edges = listEdges(db, projectId);
|
|
205
|
+
const tasks = (await listTasks(db, projectId)).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
206
|
+
const edges = await listEdges(db, projectId);
|
|
168
207
|
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
169
|
-
const tagsByTask = listTagsByTaskForProject(db, projectId);
|
|
208
|
+
const tagsByTask = await listTagsByTaskForProject(db, projectId);
|
|
170
209
|
const tagMatches = filter.tags !== undefined && filter.tags.length > 0
|
|
171
|
-
? taskIdsWithAnyTagName(db, projectId, filter.tags.map(normalizeTagName))
|
|
210
|
+
? await taskIdsWithAnyTagName(db, projectId, filter.tags.map(normalizeTagName))
|
|
172
211
|
: undefined;
|
|
173
212
|
const serialize = (task) => serializeTask(task, tagsByTask.get(task.id) ?? []);
|
|
174
213
|
const prerequisites = new Map();
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { type Db } from '@plandesk/db';
|
|
2
|
-
|
|
2
|
+
import { type OrgScopedDeps } from './org-scope.js';
|
|
3
|
+
export type TokenServiceDeps = OrgScopedDeps & {
|
|
3
4
|
db: Db;
|
|
4
5
|
};
|
|
5
6
|
export declare function createTokenService(deps: TokenServiceDeps): {
|
|
6
|
-
create(name: string): {
|
|
7
|
+
create(name: string): Promise<{
|
|
7
8
|
id: string;
|
|
8
9
|
name: string;
|
|
9
10
|
token: string;
|
|
10
|
-
}
|
|
11
|
-
list(): import("../serialize.js").SerializedToken[]
|
|
12
|
-
revoke(id: string): import("../serialize.js").SerializedToken | undefined
|
|
11
|
+
}>;
|
|
12
|
+
list(): Promise<import("../serialize.js").SerializedToken[]>;
|
|
13
|
+
revoke(id: string): Promise<import("../serialize.js").SerializedToken | undefined>;
|
|
13
14
|
};
|
|
14
15
|
export type TokenService = ReturnType<typeof createTokenService>;
|
|
15
16
|
//# sourceMappingURL=tokens.d.ts.map
|
package/dist/services/tokens.js
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { createToken as dbCreateToken, listTokens as dbListTokens, revokeToken as dbRevokeToken, } from '@plandesk/db';
|
|
2
2
|
import { serializeToken } from '../serialize.js';
|
|
3
|
+
import { assertPermission, resolveOrgId } from './org-scope.js';
|
|
3
4
|
export function createTokenService(deps) {
|
|
4
5
|
const { db } = deps;
|
|
5
6
|
return {
|
|
6
|
-
create(name) {
|
|
7
|
-
|
|
7
|
+
async create(name) {
|
|
8
|
+
assertPermission(deps, 'apiKey', 'create');
|
|
9
|
+
const orgId = resolveOrgId(deps);
|
|
10
|
+
const result = await dbCreateToken(db, { name, orgId });
|
|
8
11
|
return {
|
|
9
12
|
id: result.id,
|
|
10
13
|
name: result.name,
|
|
11
14
|
token: result.token,
|
|
12
15
|
};
|
|
13
16
|
},
|
|
14
|
-
list() {
|
|
15
|
-
|
|
17
|
+
async list() {
|
|
18
|
+
assertPermission(deps, 'apiKey', 'read');
|
|
19
|
+
const orgId = resolveOrgId(deps);
|
|
20
|
+
return (await dbListTokens(db, orgId)).map(serializeToken);
|
|
16
21
|
},
|
|
17
|
-
revoke(id) {
|
|
18
|
-
|
|
22
|
+
async revoke(id) {
|
|
23
|
+
assertPermission(deps, 'apiKey', 'delete');
|
|
24
|
+
const orgId = resolveOrgId(deps);
|
|
25
|
+
const revoked = await dbRevokeToken(db, id, orgId);
|
|
19
26
|
if (!revoked) {
|
|
20
27
|
return undefined;
|
|
21
28
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
/** Portal guest session after named join (share-scoped; no org membership). */
|
|
3
|
+
export declare const GUEST_SESSION_COOKIE = "plandesk_guest";
|
|
4
|
+
export declare function readGuestSessionCookie(c: Context): string | undefined;
|
|
5
|
+
//# sourceMappingURL=session.d.ts.map
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getCookie } from 'hono/cookie';
|
|
2
|
+
/** Portal guest session after named join (share-scoped; no org membership). */
|
|
3
|
+
export const GUEST_SESSION_COOKIE = 'plandesk_guest';
|
|
4
|
+
export function readGuestSessionCookie(c) {
|
|
5
|
+
const raw = getCookie(c, GUEST_SESSION_COOKIE);
|
|
6
|
+
return raw !== undefined && raw.length > 0 ? raw : undefined;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=session.js.map
|
package/dist/storage/local.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { createFile, getFile } from '@plandesk/db';
|
|
2
|
+
import { createFile, getFile, getFileInOrg } from '@plandesk/db';
|
|
3
|
+
import { tryGetAuthContext } from '../auth-context.js';
|
|
3
4
|
import { fileUrl } from './adapter.js';
|
|
4
5
|
export function createLocalBlobAdapter(deps) {
|
|
5
6
|
const { db } = deps;
|
|
6
7
|
return {
|
|
7
|
-
put(input) {
|
|
8
|
+
async put(input) {
|
|
8
9
|
const id = createHash('sha256').update(input.bytes).digest('hex');
|
|
9
|
-
createFile(db, {
|
|
10
|
+
await createFile(db, {
|
|
10
11
|
id,
|
|
11
12
|
projectId: input.projectId,
|
|
12
13
|
filename: input.filename,
|
|
@@ -14,20 +15,25 @@ export function createLocalBlobAdapter(deps) {
|
|
|
14
15
|
size: input.bytes.length,
|
|
15
16
|
bytes: input.bytes,
|
|
16
17
|
});
|
|
17
|
-
return
|
|
18
|
+
return { id, url: fileUrl(id) };
|
|
18
19
|
},
|
|
19
|
-
resolve(id) {
|
|
20
|
-
const
|
|
20
|
+
async resolve(id) {
|
|
21
|
+
const auth = tryGetAuthContext();
|
|
22
|
+
const file = auth !== undefined && auth.kind !== 'guest'
|
|
23
|
+
? await getFileInOrg(db, id, auth.orgId)
|
|
24
|
+
: // Fallback for non-request paths: content hash is not globally unique;
|
|
25
|
+
// without org context we cannot safely resolve.
|
|
26
|
+
undefined;
|
|
21
27
|
if (!file) {
|
|
22
|
-
return
|
|
28
|
+
return null;
|
|
23
29
|
}
|
|
24
30
|
if (file.externalUrl) {
|
|
25
|
-
return
|
|
31
|
+
return { redirectUrl: file.externalUrl };
|
|
26
32
|
}
|
|
27
33
|
if (!file.bytes) {
|
|
28
|
-
return
|
|
34
|
+
return null;
|
|
29
35
|
}
|
|
30
|
-
return
|
|
36
|
+
return { bytes: file.bytes, mime: file.mime, filename: file.filename };
|
|
31
37
|
},
|
|
32
38
|
};
|
|
33
39
|
}
|
package/dist/storage/s3.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getFileInOrg } from '@plandesk/db';
|
|
3
|
+
import { tryGetAuthContext } from '../auth-context.js';
|
|
4
|
+
import {} from './adapter.js';
|
|
4
5
|
export function readS3ConfigFromEnv(env = process.env) {
|
|
5
6
|
const bucket = env.PLANDESK_S3_BUCKET;
|
|
6
7
|
const region = env.PLANDESK_S3_REGION;
|
|
@@ -25,12 +26,16 @@ export function createS3Adapter(deps) {
|
|
|
25
26
|
const key = `${input.projectId}/${id}`;
|
|
26
27
|
return Promise.reject(new Error(`s3 adapter not built: cannot PUT "${key}" to bucket "${config.bucket}"`));
|
|
27
28
|
},
|
|
28
|
-
resolve(id) {
|
|
29
|
-
const
|
|
29
|
+
async resolve(id) {
|
|
30
|
+
const auth = tryGetAuthContext();
|
|
31
|
+
if (auth === undefined || auth.kind === 'guest') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const file = await getFileInOrg(db, id, auth.orgId);
|
|
30
35
|
if (!file?.externalUrl) {
|
|
31
|
-
return
|
|
36
|
+
return null;
|
|
32
37
|
}
|
|
33
|
-
return
|
|
38
|
+
return { redirectUrl: file.externalUrl };
|
|
34
39
|
},
|
|
35
40
|
};
|
|
36
41
|
}
|