@opengeni/api-router 0.21.9 → 0.21.10

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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAppComposition,
3
3
  startSlackInteractionPump
4
- } from "./chunk-BESOWOUQ.js";
4
+ } from "./chunk-J4JS2F7L.js";
5
5
 
6
6
  // src/index.ts
7
7
  import {
@@ -22,6 +22,7 @@ type SocialApiDeps = {
22
22
  type ConnectionRef = {
23
23
  workspaceId: string;
24
24
  connectionId: string;
25
+ subjectId?: string | null;
25
26
  };
26
27
  export declare function socialSearchLive(deps: SocialApiDeps, ref: ConnectionRef, input: {
27
28
  query: string;
@@ -71,6 +71,7 @@ export declare function completeSocialOAuthCallback(deps: SocialOAuthDeps, input
71
71
  export declare function freshSocialAccessToken(deps: SocialOAuthDeps, ref: {
72
72
  workspaceId: string;
73
73
  connectionId: string;
74
+ subjectId?: string | null;
74
75
  }): Promise<{
75
76
  connection: SocialConnection;
76
77
  bundle: SocialCredentialBundle;
@@ -78,6 +79,7 @@ export declare function freshSocialAccessToken(deps: SocialOAuthDeps, ref: {
78
79
  export declare function markNeedsReauth(deps: SocialOAuthDeps, ref: {
79
80
  workspaceId: string;
80
81
  connectionId: string;
82
+ subjectId?: string | null;
81
83
  }): Promise<void>;
82
84
  export declare function parseSocialCredentialBundle(raw: string): SocialCredentialBundle;
83
85
  export declare function socialTokenNeedsRefresh(bundle: SocialCredentialBundle, now: Date): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "0.21.9",
3
+ "version": "0.21.10",
4
4
  "description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -44,17 +44,17 @@
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "@opengeni/agent-proto": "^0.3.0",
46
46
  "@opengeni/codex": "^0.2.10",
47
- "@opengeni/config": "^0.10.11",
48
- "@opengeni/contracts": "^0.38.1",
49
- "@opengeni/core": "^0.20.11",
50
- "@opengeni/db": "^0.27.8",
51
- "@opengeni/documents": "^0.5.4",
52
- "@opengeni/events": "^0.3.74",
53
- "@opengeni/github": "^0.4.27",
47
+ "@opengeni/config": "^0.10.12",
48
+ "@opengeni/contracts": "^0.38.2",
49
+ "@opengeni/core": "^0.20.12",
50
+ "@opengeni/db": "^0.27.9",
51
+ "@opengeni/documents": "^0.5.5",
52
+ "@opengeni/events": "^0.3.75",
53
+ "@opengeni/github": "^0.4.28",
54
54
  "@opengeni/network": "^0.2.0",
55
- "@opengeni/observability": "^0.4.14",
56
- "@opengeni/runtime": "^0.18.10",
57
- "@opengeni/storage": "^0.2.64",
55
+ "@opengeni/observability": "^0.4.15",
56
+ "@opengeni/runtime": "^0.18.11",
57
+ "@opengeni/storage": "^0.2.65",
58
58
  "@temporalio/client": "^1.17.0",
59
59
  "better-auth": "^1.6.14",
60
60
  "hono": "^4.12.18",
@@ -35,7 +35,7 @@ type SocialApiDeps = {
35
35
  providerFetch?: SocialProviderFetch | undefined;
36
36
  };
37
37
 
38
- type ConnectionRef = { workspaceId: string; connectionId: string };
38
+ type ConnectionRef = { workspaceId: string; connectionId: string; subjectId?: string | null };
39
39
 
40
40
  const MAX_LIVE_RESULTS = 50;
41
41
 
@@ -141,6 +141,7 @@ type SocialOAuthStatePayload = {
141
141
  accountId: string;
142
142
  workspaceId: string;
143
143
  subjectId: string;
144
+ ownership: "workspace" | "personal";
144
145
  provider: SocialOAuthProviderId;
145
146
  scopes: string[];
146
147
  encryptedPkceVerifier?: string;
@@ -190,6 +191,7 @@ export async function startSocialOAuth(
190
191
  accountId: context.accountId,
191
192
  workspaceId: context.workspaceId,
192
193
  subjectId: context.subjectId,
194
+ ownership: context.payload.ownership,
193
195
  provider: provider.id,
194
196
  scopes,
195
197
  ...(verifier && key ? { encryptedPkceVerifier: encryptEnvironmentValue(key, verifier) } : {}),
@@ -276,7 +278,7 @@ export async function completeSocialOAuthCallback(
276
278
  if (
277
279
  !grant ||
278
280
  grant.accountId !== state.accountId ||
279
- !hasPermission(grant.permissions, "workspace:admin")
281
+ (state.ownership === "workspace" && !hasPermission(grant.permissions, "workspace:admin"))
280
282
  ) {
281
283
  throw new HTTPException(403, {
282
284
  message: "OAuth subject no longer has permission to connect social accounts",
@@ -314,6 +316,7 @@ export async function completeSocialOAuthCallback(
314
316
  const connection = await upsertSocialOAuthConnection(db, {
315
317
  accountId: state.accountId,
316
318
  workspaceId: state.workspaceId,
319
+ subjectId: state.ownership === "personal" ? state.subjectId : null,
317
320
  provider: provider.id,
318
321
  accountHandle: identity.handle,
319
322
  accountName: identity.name ?? null,
@@ -347,10 +350,15 @@ export async function completeSocialOAuthCallback(
347
350
  */
348
351
  export async function freshSocialAccessToken(
349
352
  deps: SocialOAuthDeps,
350
- ref: { workspaceId: string; connectionId: string },
353
+ ref: { workspaceId: string; connectionId: string; subjectId?: string | null },
351
354
  ): Promise<{ connection: SocialConnection; bundle: SocialCredentialBundle }> {
352
355
  const { db, settings } = deps;
353
- const loaded = await loadSocialConnectionCredential(db, ref.workspaceId, ref.connectionId);
356
+ const loaded = await loadSocialConnectionCredential(
357
+ db,
358
+ ref.workspaceId,
359
+ ref.connectionId,
360
+ ref.subjectId,
361
+ );
354
362
  if (!loaded) {
355
363
  throw new Error(`Social connection not found: ${ref.connectionId}`);
356
364
  }
@@ -397,7 +405,12 @@ export async function freshSocialAccessToken(
397
405
  // rotates refresh tokens per use, so the loser's token is already spent.
398
406
  // Re-read before declaring the connection dead — if another writer
399
407
  // persisted a newer bundle, use that instead of flipping needs_reauth.
400
- const reloaded = await loadSocialConnectionCredential(db, ref.workspaceId, ref.connectionId);
408
+ const reloaded = await loadSocialConnectionCredential(
409
+ db,
410
+ ref.workspaceId,
411
+ ref.connectionId,
412
+ ref.subjectId,
413
+ );
401
414
  if (
402
415
  reloaded?.credentialEncrypted &&
403
416
  reloaded.credentialEncrypted !== loaded.credentialEncrypted
@@ -429,6 +442,7 @@ export async function freshSocialAccessToken(
429
442
  (await updateSocialConnectionCredential(db, {
430
443
  workspaceId: ref.workspaceId,
431
444
  connectionId: ref.connectionId,
445
+ ...(ref.subjectId !== undefined ? { subjectId: ref.subjectId } : {}),
432
446
  credentialEncrypted: encryptEnvironmentValue(key, JSON.stringify(refreshed)),
433
447
  status: "connected",
434
448
  tokenMetadata: publicTokenMetadata(refreshed),
@@ -438,11 +452,12 @@ export async function freshSocialAccessToken(
438
452
 
439
453
  export async function markNeedsReauth(
440
454
  deps: SocialOAuthDeps,
441
- ref: { workspaceId: string; connectionId: string },
455
+ ref: { workspaceId: string; connectionId: string; subjectId?: string | null },
442
456
  ): Promise<void> {
443
457
  await updateSocialConnectionCredential(deps.db, {
444
458
  workspaceId: ref.workspaceId,
445
459
  connectionId: ref.connectionId,
460
+ ...(ref.subjectId !== undefined ? { subjectId: ref.subjectId } : {}),
446
461
  status: "needs_reauth",
447
462
  });
448
463
  }
@@ -671,6 +686,7 @@ function readSocialOAuthState(
671
686
  accountId: requiredStateString(payload.accountId, "accountId"),
672
687
  workspaceId: requiredStateString(payload.workspaceId, "workspaceId"),
673
688
  subjectId: requiredStateString(payload.subjectId, "subjectId"),
689
+ ownership: payload.ownership === "personal" ? "personal" : "workspace",
674
690
  provider,
675
691
  scopes: Array.isArray(payload.scopes)
676
692
  ? payload.scopes.filter((scope): scope is string => typeof scope === "string")
package/src/mcp/server.ts CHANGED
@@ -55,7 +55,6 @@ import {
55
55
  listRigs,
56
56
  listRigChangeMonitoringSummaries,
57
57
  listRigVersionMonitoringSummaries,
58
- listSocialConnections,
59
58
  listSocialPosts,
60
59
  recordAuditEvent,
61
60
  recordSyncedSocialPosts,
@@ -99,6 +98,7 @@ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
99
98
  import * as z4 from "zod/v4";
100
99
  import {
101
100
  hasPermission,
101
+ authorizedSocialConnectionsForGrant,
102
102
  requireSessionAuthorization,
103
103
  requireSessionAuthorizationListScope,
104
104
  type ResolvedSessionAuthorization,
@@ -360,6 +360,20 @@ export function buildOpenGeniMcpServer(
360
360
  });
361
361
  const can = (permission: Permission) => hasPermission(grant.permissions, permission);
362
362
  const toolspaceMode = options.toolspace != null;
363
+ let socialConnectionsPromise: ReturnType<typeof authorizedSocialConnectionsForGrant> | undefined;
364
+ const authorizedSocialConnections = () =>
365
+ (socialConnectionsPromise ??= authorizedSocialConnectionsForGrant({
366
+ db: deps.db,
367
+ grant,
368
+ limit: 500,
369
+ }));
370
+ const requireAuthorizedSocialConnection = async (connectionId: string) => {
371
+ const authority = (await authorizedSocialConnections()).find(
372
+ ({ connection }) => connection.id === connectionId,
373
+ );
374
+ if (!authority) throw new Error(`Unknown or unavailable social connection: ${connectionId}`);
375
+ return authority;
376
+ };
363
377
 
364
378
  // Session-scoped tools key off the worker-asserted sessionId claim (signed
365
379
  // into the delegated token by the worker, never agent-controlled).
@@ -490,11 +504,9 @@ export function buildOpenGeniMcpServer(
490
504
  },
491
505
  async ({ limit }) =>
492
506
  json({
493
- connections: await listSocialConnections(
494
- deps.db,
495
- grant.workspaceId,
496
- boundedMcpLimit(limit),
497
- ),
507
+ connections: (await authorizedSocialConnections())
508
+ .slice(0, boundedMcpLimit(limit))
509
+ .map(({ connection }) => connection),
498
510
  }),
499
511
  );
500
512
 
@@ -510,6 +522,15 @@ export function buildOpenGeniMcpServer(
510
522
  },
511
523
  },
512
524
  async ({ connectionIds, since, windowHours, limit }) => {
525
+ const authorized = await authorizedSocialConnections();
526
+ const selected = connectionIds?.length
527
+ ? connectionIds.map((id) => {
528
+ const match = authorized.find(({ connection }) => connection.id === id);
529
+ if (!match) throw new Error(`Unknown or unavailable social connection: ${id}`);
530
+ return match;
531
+ })
532
+ : authorized;
533
+ const personalSubjectId = selected.find(({ subjectId }) => subjectId)?.subjectId ?? null;
513
534
  const sinceDate = since
514
535
  ? parseMcpDate(since, "since")
515
536
  : new Date(Date.now() - (windowHours ?? 24) * 60 * 60 * 1000);
@@ -517,7 +538,8 @@ export function buildOpenGeniMcpServer(
517
538
  since: sinceDate.toISOString(),
518
539
  posts: await listSocialPosts(deps.db, {
519
540
  workspaceId: grant.workspaceId,
520
- ...(connectionIds?.length ? { connectionIds } : {}),
541
+ subjectId: personalSubjectId,
542
+ connectionIds: selected.map(({ connection }) => connection.id),
521
543
  since: sinceDate,
522
544
  limit: boundedMcpLimit(limit),
523
545
  }),
@@ -539,7 +561,8 @@ export function buildOpenGeniMcpServer(
539
561
  },
540
562
  },
541
563
  async ({ connectionIds, documentBaseIds, since, windowHours, limit }) => {
542
- const allConnections = await listSocialConnections(deps.db, grant.workspaceId, 500);
564
+ const authorized = await authorizedSocialConnections();
565
+ const allConnections = authorized.map(({ connection }) => connection);
543
566
  const selectedIds =
544
567
  connectionIds && connectionIds.length > 0 ? new Set(connectionIds) : null;
545
568
  const connections = selectedIds
@@ -559,6 +582,11 @@ export function buildOpenGeniMcpServer(
559
582
  connections.length > 0
560
583
  ? await listSocialPosts(deps.db, {
561
584
  workspaceId: grant.workspaceId,
585
+ subjectId:
586
+ authorized.find(
587
+ ({ connection, subjectId }) =>
588
+ subjectId && connections.some((selected) => selected.id === connection.id),
589
+ )?.subjectId ?? null,
562
590
  connectionIds: connections.map((connection) => connection.id),
563
591
  since: sinceDate,
564
592
  limit: boundedMcpLimit(limit),
@@ -597,9 +625,10 @@ export function buildOpenGeniMcpServer(
597
625
  },
598
626
  },
599
627
  async ({ connectionId, query, subreddit, limit }) => {
628
+ const authority = await requireAuthorizedSocialConnection(connectionId);
600
629
  const result = await socialSearchLive(
601
630
  deps,
602
- { workspaceId: grant.workspaceId, connectionId },
631
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
603
632
  { query, subreddit, limit },
604
633
  );
605
634
  return json({ provider: result.connection.provider, posts: result.posts });
@@ -618,9 +647,10 @@ export function buildOpenGeniMcpServer(
618
647
  },
619
648
  },
620
649
  async ({ connectionId, sinceId, limit }) => {
650
+ const authority = await requireAuthorizedSocialConnection(connectionId);
621
651
  const result = await socialMentionsLive(
622
652
  deps,
623
- { workspaceId: grant.workspaceId, connectionId },
653
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
624
654
  { sinceId, limit },
625
655
  );
626
656
  return json({ provider: result.connection.provider, posts: result.posts });
@@ -639,9 +669,10 @@ export function buildOpenGeniMcpServer(
639
669
  },
640
670
  },
641
671
  async ({ connectionId, id, limit }) => {
672
+ const authority = await requireAuthorizedSocialConnection(connectionId);
642
673
  const result = await socialThreadLive(
643
674
  deps,
644
- { workspaceId: grant.workspaceId, connectionId },
675
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
645
676
  { id, limit },
646
677
  );
647
678
  return json({ provider: result.connection.provider, posts: result.posts });
@@ -664,9 +695,10 @@ export function buildOpenGeniMcpServer(
664
695
  },
665
696
  },
666
697
  async ({ connectionId, limit }) => {
698
+ const authority = await requireAuthorizedSocialConnection(connectionId);
667
699
  const result = await socialOwnPostsLive(
668
700
  deps,
669
- { workspaceId: grant.workspaceId, connectionId },
701
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
670
702
  { limit },
671
703
  );
672
704
  // A post without a provider timestamp is skipped rather than recorded
@@ -677,6 +709,7 @@ export function buildOpenGeniMcpServer(
677
709
  accountId: grant.accountId,
678
710
  workspaceId: grant.workspaceId,
679
711
  connectionId,
712
+ subjectId: authority.subjectId,
680
713
  posts: datedPosts.map((post) => ({
681
714
  externalPostId: post.id,
682
715
  url: post.url,
@@ -708,9 +741,10 @@ export function buildOpenGeniMcpServer(
708
741
  },
709
742
  },
710
743
  async ({ connectionId, inReplyToId, text }) => {
744
+ const authority = await requireAuthorizedSocialConnection(connectionId);
711
745
  const result = await socialPostReply(
712
746
  deps,
713
- { workspaceId: grant.workspaceId, connectionId },
747
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
714
748
  { inReplyToId, text },
715
749
  );
716
750
  // Outbound publishes leave a durable, secret-free receipt (house
@@ -22,9 +22,11 @@ export function registerCapabilityRoutes(app: Hono, deps: ApiRouteDeps): void {
22
22
 
23
23
  app.get("/v1/workspaces/:workspaceId/capabilities", async (c) => {
24
24
  const workspaceId = c.req.param("workspaceId");
25
- await requireAccessGrant(c, deps, workspaceId, "workspace:read");
25
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
26
26
  return c.json(
27
- CapabilityCatalogResponse.parse(await buildCapabilityCatalog({ db, workspaceId, settings })),
27
+ CapabilityCatalogResponse.parse(
28
+ await buildCapabilityCatalog({ db, workspaceId, settings, subjectId: grant.subjectId }),
29
+ ),
28
30
  );
29
31
  });
30
32
 
@@ -187,7 +187,12 @@ export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
187
187
  action: "schedule:create",
188
188
  quantity: 1,
189
189
  });
190
- const connections = await resolveSocialConnections(db, workspaceId, payload.connectionIds);
190
+ const connections = await resolveSocialConnections(
191
+ db,
192
+ workspaceId,
193
+ payload.connectionIds,
194
+ grant.subjectId,
195
+ );
191
196
  if (connections.length === 0) {
192
197
  throw new HTTPException(422, {
193
198
  message: "at least one connected social account is required",
@@ -254,20 +259,21 @@ async function resolveSocialConnections(
254
259
  db: ApiRouteDeps["db"],
255
260
  workspaceId: string,
256
261
  connectionIds: string[],
262
+ subjectId: string,
257
263
  ): Promise<SocialConnection[]> {
258
264
  const ids = [...new Set(connectionIds)];
259
265
  const connections =
260
266
  ids.length > 0
261
267
  ? await Promise.all(
262
268
  ids.map(async (id) => {
263
- const connection = await getSocialConnection(db, workspaceId, id);
269
+ const connection = await getSocialConnection(db, workspaceId, id, subjectId);
264
270
  if (!connection) {
265
271
  throw new HTTPException(422, { message: `unknown social connection: ${id}` });
266
272
  }
267
273
  return connection;
268
274
  }),
269
275
  )
270
- : (await listSocialConnections(db, workspaceId, 500)).filter(
276
+ : (await listSocialConnections(db, workspaceId, 500, subjectId)).filter(
271
277
  (connection) => connection.status === "connected",
272
278
  );
273
279
  const inactive = connections.find((connection) => connection.status !== "connected");
@@ -7,6 +7,7 @@ import {
7
7
  import {
8
8
  createSocialConnection,
9
9
  createSocialPost,
10
+ getSocialConnection,
10
11
  listSocialConnections,
11
12
  listSocialPosts,
12
13
  updateSocialConnectionCredential,
@@ -24,8 +25,15 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
24
25
 
25
26
  app.get("/v1/workspaces/:workspaceId/social/connections", async (c) => {
26
27
  const workspaceId = c.req.param("workspaceId");
27
- await requireAccessGrant(c, deps, workspaceId, "workspace:read");
28
- return c.json(await listSocialConnections(db, workspaceId, boundedLimit(c.req.query("limit"))));
28
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
29
+ return c.json(
30
+ await listSocialConnections(
31
+ db,
32
+ workspaceId,
33
+ boundedLimit(c.req.query("limit")),
34
+ grant.subjectId,
35
+ ),
36
+ );
29
37
  });
30
38
 
31
39
  app.post("/v1/workspaces/:workspaceId/social/connections", async (c) => {
@@ -59,10 +67,16 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
59
67
  // reconnecting via the OAuth flow revives it.
60
68
  app.delete("/v1/workspaces/:workspaceId/social/connections/:connectionId", async (c) => {
61
69
  const workspaceId = c.req.param("workspaceId");
62
- await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
70
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
71
+ const connectionId = c.req.param("connectionId");
72
+ const existing = await getSocialConnection(db, workspaceId, connectionId, grant.subjectId);
73
+ if (!existing) throw new HTTPException(404, { message: "social connection not found" });
74
+ if (existing.ownership === "workspace")
75
+ await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
63
76
  const connection = await updateSocialConnectionCredential(db, {
64
77
  workspaceId,
65
- connectionId: c.req.param("connectionId"),
78
+ connectionId,
79
+ subjectId: existing.ownership === "personal" ? grant.subjectId : null,
66
80
  credentialEncrypted: null,
67
81
  status: "disabled",
68
82
  tokenMetadata: {},
@@ -78,7 +92,6 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
78
92
  // callback is unauthenticated (browser redirect) but bound by signed state.
79
93
  app.post("/v1/workspaces/:workspaceId/social/oauth/start", async (c) => {
80
94
  const workspaceId = c.req.param("workspaceId");
81
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
82
95
  const parsed = SocialOAuthStartRequest.safeParse(await c.req.json());
83
96
  if (!parsed.success) {
84
97
  throw new HTTPException(400, {
@@ -86,6 +99,12 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
86
99
  });
87
100
  }
88
101
  const payload = parsed.data;
102
+ const grant = await requireAccessGrant(
103
+ c,
104
+ deps,
105
+ workspaceId,
106
+ payload.ownership === "workspace" ? "workspace:admin" : "workspace:read",
107
+ );
89
108
  const result = await startSocialOAuth(
90
109
  { db, settings, observability },
91
110
  {
@@ -114,7 +133,7 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
114
133
 
115
134
  app.get("/v1/workspaces/:workspaceId/social/posts", async (c) => {
116
135
  const workspaceId = c.req.param("workspaceId");
117
- await requireAccessGrant(c, deps, workspaceId, "workspace:read");
136
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
118
137
  const since = parseSince(c.req.query("since"));
119
138
  const connectionIds = parseConnectionIds(
120
139
  c.req.query("connectionIds") ?? c.req.query("connectionId"),
@@ -122,6 +141,7 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
122
141
  return c.json(
123
142
  await listSocialPosts(db, {
124
143
  workspaceId,
144
+ subjectId: grant.subjectId,
125
145
  ...(connectionIds?.length ? { connectionIds } : {}),
126
146
  ...(since ? { since } : {}),
127
147
  limit: boundedLimit(c.req.query("limit")),
@@ -138,6 +158,7 @@ export function registerSocialRoutes(app: Hono, deps: ApiRouteDeps): void {
138
158
  await createSocialPost(db, {
139
159
  accountId: grant.accountId,
140
160
  workspaceId,
161
+ subjectId: grant.subjectId,
141
162
  connectionId: payload.connectionId,
142
163
  externalPostId: payload.externalPostId ?? null,
143
164
  url: payload.url ?? null,