139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
// Seed a Slack `chat_installations` row (+ OAuth credentials) into the current
|
|
// worktree's DB so the dev Slack app works without a manual OAuth install per
|
|
// worktree. Reuses the prod `replaceInstallation()` so the seed matches reality.
|
|
//
|
|
// Invoked by `bun dw setup` (after the test org is seeded) with DATABASE_URL
|
|
// pointed at the worktree branch; also runnable standalone:
|
|
// ENV_FILE=.env infisical run --env=dev --recursive -- \
|
|
// bun apps/leaf/scripts/seedSlackInstall.ts (DATABASE_URL=<worktree>)
|
|
//
|
|
// Needs SLACK_BOT_TOKEN (the app's Bot User OAuth Token, xoxb-…) in the env.
|
|
// SLACK_CLIENT_ID / SLACK_CLIENT_SECRET configure OAuth, but cannot mint a bot
|
|
// token without an install callback code, so this skips cleanly if absent.
|
|
import crypto from "node:crypto";
|
|
import {
|
|
AppEnv,
|
|
type ChatInstallState,
|
|
member,
|
|
organizations,
|
|
} from "@autumn/shared";
|
|
import { eq } from "drizzle-orm";
|
|
import { db } from "../src/lib/db.js";
|
|
import { replaceInstallation } from "../src/providers/slack/installations.js";
|
|
|
|
// The dev test org seeded by scripts/setupTestUtils/createTestOrg.ts.
|
|
const SEED_ORG_SLUG = "unit-test-org";
|
|
const SEED_ORG_ID_FALLBACK = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt";
|
|
const SEED_USER_FALLBACK = "user_setup_test_inviter";
|
|
|
|
// Stored on the installation but not consulted at runtime; mirror the dev app's
|
|
// bot scopes for fidelity.
|
|
const SLACK_BOT_SCOPES = [
|
|
"app_mentions:read",
|
|
"assistant:write",
|
|
"channels:history",
|
|
"channels:read",
|
|
"chat:write",
|
|
"files:read",
|
|
"groups:history",
|
|
"groups:read",
|
|
"im:history",
|
|
"im:read",
|
|
"im:write",
|
|
"mpim:history",
|
|
"mpim:read",
|
|
"users:read",
|
|
];
|
|
|
|
const log = (message: string) => console.log(`[seed-slack] ${message}`);
|
|
|
|
const main = async () => {
|
|
const botToken = process.env.SLACK_BOT_TOKEN;
|
|
if (!botToken) {
|
|
log(
|
|
"skipping: SLACK_BOT_TOKEN not set (client id/secret are not enough to seed an installed bot)",
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Identify the workspace + bot user from the token.
|
|
const response = await fetch("https://slack.com/api/auth.test", {
|
|
headers: { Authorization: `Bearer ${botToken}` },
|
|
method: "POST",
|
|
});
|
|
const auth = (await response.json()) as {
|
|
ok: boolean;
|
|
error?: string;
|
|
team_id?: string;
|
|
team?: string;
|
|
user_id?: string;
|
|
};
|
|
if (!auth.ok || !auth.team_id) {
|
|
log(`skipping: Slack auth.test failed (${auth.error ?? "unknown"})`);
|
|
return;
|
|
}
|
|
|
|
// Resolve the seeded org (by slug, then by known id) — bail if setup-test
|
|
// hasn't run yet.
|
|
const [bySlug] = await db
|
|
.select({ id: organizations.id, slug: organizations.slug })
|
|
.from(organizations)
|
|
.where(eq(organizations.slug, SEED_ORG_SLUG))
|
|
.limit(1);
|
|
let orgId = bySlug?.id;
|
|
if (!orgId) {
|
|
const [byId] = await db
|
|
.select({ id: organizations.id })
|
|
.from(organizations)
|
|
.where(eq(organizations.id, SEED_ORG_ID_FALLBACK))
|
|
.limit(1);
|
|
orgId = byId?.id;
|
|
}
|
|
if (!orgId) {
|
|
log(
|
|
`skipping: org '${SEED_ORG_SLUG}' not found (run the test-org seed first)`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// A member user owns the install/OAuth consent; fall back to the pinned inviter.
|
|
const [memberRow] = await db
|
|
.select({ userId: member.userId })
|
|
.from(member)
|
|
.where(eq(member.organizationId, orgId))
|
|
.limit(1);
|
|
const userId = memberRow?.userId ?? SEED_USER_FALLBACK;
|
|
|
|
const state: ChatInstallState = {
|
|
env: AppEnv.Sandbox,
|
|
expiresAt: Date.now() + 600_000,
|
|
nonce: crypto.randomUUID(),
|
|
orgId,
|
|
provider: "slack",
|
|
userId,
|
|
};
|
|
|
|
await replaceInstallation({
|
|
botAccessToken: botToken,
|
|
botUserId: auth.user_id,
|
|
provider: "slack",
|
|
scopes: SLACK_BOT_SCOPES,
|
|
state,
|
|
workspaceId: auth.team_id,
|
|
workspaceName: auth.team ?? auth.team_id,
|
|
});
|
|
|
|
log(
|
|
`seeded slack installation for org ${bySlug?.slug ?? orgId} (workspace ${auth.team_id})`,
|
|
);
|
|
};
|
|
|
|
main()
|
|
.then(() => process.exit(0))
|
|
.catch((error) => {
|
|
console.error(
|
|
`[seed-slack] failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
process.exit(1);
|
|
});
|