@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.
Files changed (101) hide show
  1. package/dist/access-control.d.ts +150 -0
  2. package/dist/access-control.js +63 -0
  3. package/dist/agent-keys.d.ts +81 -0
  4. package/dist/agent-keys.js +181 -0
  5. package/dist/auth-context.d.ts +60 -0
  6. package/dist/auth-context.js +41 -0
  7. package/dist/auth.d.ts +33 -0
  8. package/dist/auth.js +357 -0
  9. package/dist/better-auth.d.ts +66 -0
  10. package/dist/better-auth.js +106 -0
  11. package/dist/github.d.ts +57 -0
  12. package/dist/github.js +115 -0
  13. package/dist/hosted-auth.d.ts +23 -0
  14. package/dist/hosted-auth.js +49 -0
  15. package/dist/identity.d.ts +38 -0
  16. package/dist/identity.js +163 -0
  17. package/dist/index.d.ts +13 -2
  18. package/dist/index.js +12 -2
  19. package/dist/invitations.d.ts +94 -0
  20. package/dist/invitations.js +211 -0
  21. package/dist/organizations.d.ts +32 -0
  22. package/dist/organizations.js +63 -0
  23. package/dist/permissions.d.ts +19 -0
  24. package/dist/permissions.js +42 -0
  25. package/dist/projection.d.ts +1 -1
  26. package/dist/projection.js +5 -5
  27. package/dist/routes/agent-runs.js +3 -3
  28. package/dist/routes/artifacts.js +6 -6
  29. package/dist/routes/auth.d.ts +13 -0
  30. package/dist/routes/auth.js +83 -0
  31. package/dist/routes/canvas.js +5 -5
  32. package/dist/routes/comments.js +11 -11
  33. package/dist/routes/documents.js +10 -10
  34. package/dist/routes/folders.js +8 -8
  35. package/dist/routes/goals.js +11 -11
  36. package/dist/routes/notes.js +8 -8
  37. package/dist/routes/orgs.d.ts +17 -0
  38. package/dist/routes/orgs.js +241 -0
  39. package/dist/routes/projects.js +13 -13
  40. package/dist/routes/shares.js +124 -2
  41. package/dist/routes/submissions.js +7 -8
  42. package/dist/routes/tags.js +6 -6
  43. package/dist/routes/tasks.js +14 -3
  44. package/dist/routes/tokens.js +4 -4
  45. package/dist/serialize.d.ts +3 -3
  46. package/dist/server.d.ts +20 -2
  47. package/dist/server.js +57 -9
  48. package/dist/services/agent-runs.d.ts +13 -14
  49. package/dist/services/agent-runs.js +39 -41
  50. package/dist/services/artifacts.d.ts +6 -7
  51. package/dist/services/artifacts.js +30 -26
  52. package/dist/services/canvas.d.ts +11 -12
  53. package/dist/services/canvas.js +58 -41
  54. package/dist/services/comments.d.ts +11 -12
  55. package/dist/services/comments.js +44 -64
  56. package/dist/services/documents.d.ts +10 -11
  57. package/dist/services/documents.js +74 -55
  58. package/dist/services/files.d.ts +2 -1
  59. package/dist/services/files.js +13 -5
  60. package/dist/services/folders.d.ts +7 -8
  61. package/dist/services/folders.js +45 -33
  62. package/dist/services/goals.d.ts +24 -25
  63. package/dist/services/goals.js +60 -53
  64. package/dist/services/index.d.ts +2 -5
  65. package/dist/services/index.js +15 -20
  66. package/dist/services/notes.d.ts +7 -8
  67. package/dist/services/notes.js +35 -23
  68. package/dist/services/org-scope.d.ts +22 -0
  69. package/dist/services/org-scope.js +38 -0
  70. package/dist/services/projects.d.ts +12 -13
  71. package/dist/services/projects.js +90 -58
  72. package/dist/services/scope.d.ts +12 -0
  73. package/dist/services/scope.js +29 -0
  74. package/dist/services/share.d.ts +74 -10
  75. package/dist/services/share.js +213 -23
  76. package/dist/services/sync.d.ts +7 -29
  77. package/dist/services/sync.js +37 -142
  78. package/dist/services/tags.d.ts +6 -7
  79. package/dist/services/tags.js +34 -22
  80. package/dist/services/tasks.d.ts +24 -17
  81. package/dist/services/tasks.js +97 -58
  82. package/dist/services/tokens.d.ts +6 -5
  83. package/dist/services/tokens.js +13 -6
  84. package/dist/session.d.ts +5 -0
  85. package/dist/session.js +8 -0
  86. package/dist/storage/local.js +16 -10
  87. package/dist/storage/s3.js +11 -6
  88. package/dist/test-ba-org.d.ts +16 -0
  89. package/dist/test-ba-org.js +35 -0
  90. package/dist/test-helpers.d.ts +21 -5
  91. package/dist/test-helpers.js +45 -8
  92. package/dist/vercel.d.ts +3 -0
  93. package/dist/vercel.js +62 -0
  94. package/dist/worker.d.ts +44 -0
  95. package/dist/worker.js +101 -0
  96. package/package.json +8 -5
  97. package/web/assets/index-D29SyYFS.css +2 -0
  98. package/web/assets/index-tBJ_K3ur.js +358 -0
  99. package/web/index.html +2 -2
  100. package/web/assets/index-CZs-KaPG.css +0 -2
  101. package/web/assets/index-zUcwwGtr.js +0 -358
@@ -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
- export type TagServiceDeps = {
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
@@ -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, eventBus } = deps;
19
+ const { db } = deps;
18
20
  return {
19
- list(projectId) {
20
- const project = getProject(db, projectId);
21
- if (!project) {
22
- return undefined;
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
- const project = getProject(db, projectId);
28
- if (!project) {
29
- return undefined;
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
- const existing = dbGetTag(db, id);
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
- const existing = dbGetTag(db, id);
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
  };
@@ -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 TaskServiceDeps = {
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: "scope" | "todo" | "in_progress" | "done" | "backlog";
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: "scope" | "todo" | "in_progress" | "done" | "backlog";
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: "scope" | "todo" | "in_progress" | "done" | "backlog";
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: "scope" | "todo" | "in_progress" | "done" | "backlog";
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 {};
@@ -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 tag = getTagByName(db, projectId, name) ?? createTag(db, { projectId, name });
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, eventBus } = deps;
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
- const project = getProject(db, projectId);
50
- if (!project) {
51
- return undefined;
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
- const project = getProject(db, projectId);
67
- if (!project) {
68
- return undefined;
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.transaction((tx) => {
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.transaction((tx) => {
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
- const task = getTask(db, id);
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.transaction((tx) => {
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
- const project = getProject(db, projectId);
149
- if (!project) {
150
- return undefined;
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
- export type TokenServiceDeps = {
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
@@ -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
- const result = dbCreateToken(db, { name });
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
- return dbListTokens(db).map(serializeToken);
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
- const revoked = dbRevokeToken(db, id);
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
@@ -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
@@ -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 Promise.resolve({ id, url: fileUrl(id) });
18
+ return { id, url: fileUrl(id) };
18
19
  },
19
- resolve(id) {
20
- const file = getFile(db, id);
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 Promise.resolve(null);
28
+ return null;
23
29
  }
24
30
  if (file.externalUrl) {
25
- return Promise.resolve({ redirectUrl: file.externalUrl });
31
+ return { redirectUrl: file.externalUrl };
26
32
  }
27
33
  if (!file.bytes) {
28
- return Promise.resolve(null);
34
+ return null;
29
35
  }
30
- return Promise.resolve({ bytes: file.bytes, mime: file.mime, filename: file.filename });
36
+ return { bytes: file.bytes, mime: file.mime, filename: file.filename };
31
37
  },
32
38
  };
33
39
  }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { getFile } from '@plandesk/db';
3
- import { fileUrl } from './adapter.js';
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 file = getFile(db, id);
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 Promise.resolve(null);
36
+ return null;
32
37
  }
33
- return Promise.resolve({ redirectUrl: file.externalUrl });
38
+ return { redirectUrl: file.externalUrl };
34
39
  },
35
40
  };
36
41
  }