diff --git a/apps/server/src/routes/slack/oauth.test.ts b/apps/server/src/routes/slack/oauth.test.ts index b62703df..8f718f5d 100644 --- a/apps/server/src/routes/slack/oauth.test.ts +++ b/apps/server/src/routes/slack/oauth.test.ts @@ -49,7 +49,8 @@ describe("slack manifest", () => { ); }); - test("subscribes to the events the handler acts on", () => { + // Subscribed ahead of their handlers so workspaces reconnect only once. + test("subscribes to every event the incident stack needs", () => { for (const event of [ "app_uninstalled", "tokens_revoked", diff --git a/apps/server/src/routes/slack/workspace-resolver.ts b/apps/server/src/routes/slack/workspace-resolver.ts index a9006e02..09ed0896 100644 --- a/apps/server/src/routes/slack/workspace-resolver.ts +++ b/apps/server/src/routes/slack/workspace-resolver.ts @@ -1,4 +1,4 @@ -import { and, db, desc, eq } from "@openstatus/db"; +import { and, db, desc, eq, sql } from "@openstatus/db"; import { integration, selectWorkspaceSchema, @@ -17,6 +17,17 @@ export interface SlackWorkspace { scopes?: string; } +function parseScopes(raw: string | null): string { + try { + return ( + integrationDataSchema.safeParse(JSON.parse(raw ?? "{}")).data?.scopes ?? + "" + ); + } catch { + return ""; + } +} + interface IntegrationCredential { botToken: string; botUserId: string; @@ -29,7 +40,8 @@ export async function resolveWorkspace( .select({ workspaceId: integration.workspaceId, credential: integration.credential, - data: integration.data, + // Raw text: the JSON-mode column throws on a malformed legacy row. + rawData: sql`${integration.data}`, }) .from(integration) .where( @@ -64,6 +76,6 @@ export async function resolveWorkspace( workspace: parsed.data, botToken: credential.botToken, botUserId: credential.botUserId ?? "", - scopes: integrationDataSchema.safeParse(row.data).data?.scopes ?? "", + scopes: parseScopes(row.rawData), }; } diff --git a/packages/api/src/router/integration.ts b/packages/api/src/router/integration.ts index 11475bd1..dfbb226f 100644 --- a/packages/api/src/router/integration.ts +++ b/packages/api/src/router/integration.ts @@ -36,8 +36,10 @@ export const integrationRouter = createTRPCRouter({ return integrations.map((i) => ({ ...i, missingScopes: - i.name === "slack-agent" && typeof i.data.scopes === "string" - ? missingSlackScopes(i.data.scopes) + i.name === "slack-agent" + ? missingSlackScopes( + typeof i.data.scopes === "string" ? i.data.scopes : undefined, + ) : [], })); } catch (err) {