Skip to content

Commit 647fce7

Browse files
authored
Add Linear integration service (#1261)
## Problem We want to support Linear as a signal source, but there's no backend service to kick off the Linear OAuth flow. ## Changes Add `LinearIntegrationService` with a `startFlow` method that opens the Linear authorize URL in the browser (same pattern as the simplified GitHub service)
1 parent 6db8d31 commit 647fce7

6 files changed

Lines changed: 66 additions & 0 deletions

File tree

apps/code/src/main/di/container.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { FoldersService } from "../services/folders/service";
2121
import { FsService } from "../services/fs/service";
2222
import { GitService } from "../services/git/service";
2323
import { GitHubIntegrationService } from "../services/github-integration/service";
24+
import { LinearIntegrationService } from "../services/linear-integration/service";
2425
import { LlmGatewayService } from "../services/llm-gateway/service";
2526
import { McpCallbackService } from "../services/mcp-callback/service";
2627
import { NotificationService } from "../services/notification/service";
@@ -68,6 +69,9 @@ container
6869
.bind(MAIN_TOKENS.GitHubIntegrationService)
6970
.to(GitHubIntegrationService);
7071
container.bind(MAIN_TOKENS.GitService).to(GitService);
72+
container
73+
.bind(MAIN_TOKENS.LinearIntegrationService)
74+
.to(LinearIntegrationService);
7175
container.bind(MAIN_TOKENS.McpCallbackService).to(McpCallbackService);
7276
container.bind(MAIN_TOKENS.NotificationService).to(NotificationService);
7377
container.bind(MAIN_TOKENS.OAuthService).to(OAuthService);

apps/code/src/main/di/tokens.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const MAIN_TOKENS = Object.freeze({
3434
FsService: Symbol.for("Main.FsService"),
3535
GitService: Symbol.for("Main.GitService"),
3636
GitHubIntegrationService: Symbol.for("Main.GitHubIntegrationService"),
37+
LinearIntegrationService: Symbol.for("Main.LinearIntegrationService"),
3738
DeepLinkService: Symbol.for("Main.DeepLinkService"),
3839
NotificationService: Symbol.for("Main.NotificationService"),
3940
McpCallbackService: Symbol.for("Main.McpCallbackService"),
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export {
2+
type CloudRegion,
3+
cloudRegion,
4+
type StartIntegrationFlowInput as StartLinearFlowInput,
5+
type StartIntegrationFlowOutput as StartLinearFlowOutput,
6+
startIntegrationFlowInput as startLinearFlowInput,
7+
startIntegrationFlowOutput as startLinearFlowOutput,
8+
} from "../integration-flow-schemas";
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { getCloudUrlFromRegion } from "@shared/constants/oauth.js";
2+
import { shell } from "electron";
3+
import { injectable } from "inversify";
4+
import { logger } from "../../utils/logger.js";
5+
import type { CloudRegion, StartLinearFlowOutput } from "./schemas.js";
6+
7+
const log = logger.scope("linear-integration-service");
8+
9+
@injectable()
10+
export class LinearIntegrationService {
11+
public async startFlow(
12+
region: CloudRegion,
13+
projectId: number,
14+
): Promise<StartLinearFlowOutput> {
15+
try {
16+
const cloudUrl = getCloudUrlFromRegion(region);
17+
const next = `${cloudUrl}/projects/${projectId}`;
18+
const authorizeUrl = `${cloudUrl}/api/environments/${projectId}/integrations/authorize/?kind=linear&next=${encodeURIComponent(next)}`;
19+
20+
log.info("Opening Linear authorization URL in browser");
21+
await shell.openExternal(authorizeUrl);
22+
23+
return { success: true };
24+
} catch (error) {
25+
return {
26+
success: false,
27+
error: error instanceof Error ? error.message : "Unknown error",
28+
};
29+
}
30+
}
31+
}

apps/code/src/main/trpc/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { foldersRouter } from "./routers/folders";
1414
import { fsRouter } from "./routers/fs";
1515
import { gitRouter } from "./routers/git";
1616
import { githubIntegrationRouter } from "./routers/github-integration";
17+
import { linearIntegrationRouter } from "./routers/linear-integration.js";
1718
import { llmGatewayRouter } from "./routers/llm-gateway";
1819
import { logsRouter } from "./routers/logs";
1920
import { mcpCallbackRouter } from "./routers/mcp-callback";
@@ -46,6 +47,7 @@ export const trpcRouter = router({
4647
fs: fsRouter,
4748
git: gitRouter,
4849
githubIntegration: githubIntegrationRouter,
50+
linearIntegration: linearIntegrationRouter,
4951
llmGateway: llmGatewayRouter,
5052
mcpCallback: mcpCallbackRouter,
5153
notification: notificationRouter,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { container } from "../../di/container.js";
2+
import { MAIN_TOKENS } from "../../di/tokens.js";
3+
import {
4+
startLinearFlowInput,
5+
startLinearFlowOutput,
6+
} from "../../services/linear-integration/schemas.js";
7+
import type { LinearIntegrationService } from "../../services/linear-integration/service.js";
8+
import { publicProcedure, router } from "../trpc.js";
9+
10+
const getService = () =>
11+
container.get<LinearIntegrationService>(MAIN_TOKENS.LinearIntegrationService);
12+
13+
export const linearIntegrationRouter = router({
14+
startFlow: publicProcedure
15+
.input(startLinearFlowInput)
16+
.output(startLinearFlowOutput)
17+
.mutation(({ input }) =>
18+
getService().startFlow(input.region, input.projectId),
19+
),
20+
});

0 commit comments

Comments
 (0)