Skip to content

Commit 8174cc9

Browse files
committed
Implement GitHub integration with CORS support, API client, and connection handling
1 parent 04600bf commit 8174cc9

15 files changed

Lines changed: 402 additions & 14 deletions

File tree

apps/api/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from fastapi import FastAPI, Request
22
from fastapi.responses import JSONResponse
3+
from fastapi.middleware.cors import CORSMiddleware
34
from packages.config.settings import Settings
45
from apps.api.utils import APIResponse, APIException
56
from apps.api.routes import api_router
@@ -12,6 +13,15 @@
1213
version="0.1.0"
1314
)
1415

16+
# Configure CORS
17+
app.add_middleware(
18+
CORSMiddleware,
19+
allow_origins=settings.API_CORS_ORIGINS,
20+
allow_credentials=True,
21+
allow_methods=["*"],
22+
allow_headers=["*"],
23+
)
24+
1525

1626
# Global exception handler
1727
@app.exception_handler(APIException)

apps/api/routes/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import APIRouter
22
from apps.api.routes.v1 import v1_router
33

4-
api_router = APIRouter()
4+
api_router = APIRouter(prefix="/api")
55

66
# Include versioned routes
77
api_router.include_router(v1_router)

apps/api/routes/v1/connect.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter
1+
from fastapi import APIRouter, BackgroundTasks
22
from apps.api.utils import APIResponse
33
from apps.api.routes.v1.schemas.connect import (
44
GitHubRepoConnect
@@ -10,7 +10,7 @@
1010

1111

1212
@router.post("/github")
13-
async def connect_github_repo(repo_data: GitHubRepoConnect):
13+
async def connect_github_repo(repo_data: GitHubRepoConnect, background_tasks: BackgroundTasks):
1414
repo_path = repo_data.repo_url.replace("https://github.com/", "").replace("http://github.com/", "").strip("/")
1515
parts = repo_path.split("/")
1616

@@ -21,9 +21,7 @@ async def connect_github_repo(repo_data: GitHubRepoConnect):
2121
owner = "unknown"
2222
repo = "unknown"
2323

24-
summary = process_repository(
25-
repo_data.repo_url,
26-
)
24+
background_tasks.add_task(process_repository, repo_data.repo_url)
2725

2826
return APIResponse.success(
2927
data={

apps/web/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Neo4j environment variables for graph visualization
2+
NEXT_PUBLIC_NEO4J_URI=neo4j://127.0.0.1:7687
3+
NEXT_PUBLIC_NEO4J_USER=neo4j
4+
NEXT_PUBLIC_NEO4J_PASS=10514912
5+
NEXT_PUBLIC_NEO4J_DB=demo

apps/web/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ yarn-error.log*
3131
.pnpm-debug.log*
3232

3333
# env files (can opt-in for committing if needed)
34-
.env*
34+
.env
3535

3636
# vercel
3737
.vercel

apps/web/app/integrations/page.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"use client";
2+
3+
import {
4+
Card,
5+
CardContent,
6+
CardDescription,
7+
CardFooter,
8+
CardHeader,
9+
CardTitle,
10+
} from "@/components/ui/card";
11+
import { Button } from "@/components/ui/button";
12+
import { GitHubConnectCard } from "@/components/integrations/github/github-connect-card";
13+
14+
export default function IntegrationsPage() {
15+
return (
16+
<div className="container mx-auto py-10 px-4">
17+
<div className="mb-8">
18+
<h1 className="text-3xl font-bold tracking-tight">App Integrations</h1>
19+
<p className="text-muted-foreground mt-2">
20+
Connect your favorite tools to Secrin to enhance your knowledge graph.
21+
</p>
22+
</div>
23+
24+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
25+
{/* GitHub Integration */}
26+
<GitHubConnectCard />
27+
28+
{/* Placeholder for future integrations */}
29+
<Card className="flex flex-col opacity-60">
30+
<CardHeader>
31+
<CardTitle>Jira</CardTitle>
32+
<CardDescription>Coming Soon</CardDescription>
33+
</CardHeader>
34+
<CardContent>
35+
<p className="text-sm text-muted-foreground">
36+
Connect Jira to link issues with code changes.
37+
</p>
38+
</CardContent>
39+
<CardFooter>
40+
<Button disabled variant="outline" className="w-full">
41+
Coming Soon
42+
</Button>
43+
</CardFooter>
44+
</Card>
45+
46+
<Card className="flex flex-col opacity-60">
47+
<CardHeader>
48+
<CardTitle>Slack</CardTitle>
49+
<CardDescription>Coming Soon</CardDescription>
50+
</CardHeader>
51+
<CardContent>
52+
<p className="text-sm text-muted-foreground">
53+
Get notifications and query the knowledge graph from Slack.
54+
</p>
55+
</CardContent>
56+
<CardFooter>
57+
<Button disabled variant="outline" className="w-full">
58+
Coming Soon
59+
</Button>
60+
</CardFooter>
61+
</Card>
62+
</div>
63+
</div>
64+
);
65+
}

apps/web/app/page.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
import Neo4jGraph from "@/components/Neo4jGraph";
2-
31
export default function Home() {
4-
return (
5-
<div>
6-
<Neo4jGraph />
7-
</div>
8-
);
2+
return <div>{/* Dashboard coming soon... */}</div>;
93
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { Github, CheckCircle2, AlertCircle, Loader2 } from "lucide-react";
5+
import { Button } from "@/components/ui/button";
6+
import {
7+
Card,
8+
CardContent,
9+
CardDescription,
10+
CardFooter,
11+
CardHeader,
12+
CardTitle,
13+
} from "@/components/ui/card";
14+
import { Input } from "@/components/ui/input";
15+
import { Label } from "@/components/ui/label";
16+
import { GITHUB_CONFIG } from "@/constants/integrations/github";
17+
import { GitHubService } from "@/services/integrations/github.service";
18+
19+
export function GitHubConnectCard() {
20+
const [repoUrl, setRepoUrl] = useState("");
21+
const [isLoading, setIsLoading] = useState(false);
22+
const [status, setStatus] = useState<{
23+
type: "success" | "error" | null;
24+
message: string;
25+
}>({ type: null, message: "" });
26+
27+
const handleConnect = async () => {
28+
if (!repoUrl) {
29+
setStatus({ type: "error", message: GITHUB_CONFIG.TEXT.ERROR_EMPTY_URL });
30+
return;
31+
}
32+
33+
setIsLoading(true);
34+
setStatus({ type: null, message: "" });
35+
36+
try {
37+
const response = await GitHubService.connectRepository(repoUrl);
38+
39+
if (response.success) {
40+
setStatus({
41+
type: "success",
42+
message: GITHUB_CONFIG.TEXT.SUCCESS_MESSAGE(response.data.full_name),
43+
});
44+
setRepoUrl("");
45+
} else {
46+
setStatus({
47+
type: "error",
48+
message: response.message || GITHUB_CONFIG.TEXT.ERROR_GENERIC,
49+
});
50+
}
51+
} catch (error) {
52+
setStatus({
53+
type: "error",
54+
message:
55+
error instanceof Error
56+
? error.message
57+
: GITHUB_CONFIG.TEXT.ERROR_GENERIC,
58+
});
59+
} finally {
60+
setIsLoading(false);
61+
}
62+
};
63+
64+
return (
65+
<Card className="flex flex-col">
66+
<CardHeader>
67+
<div className="flex items-center gap-2">
68+
<Github className="h-6 w-6" />
69+
<CardTitle>{GITHUB_CONFIG.TEXT.TITLE}</CardTitle>
70+
</div>
71+
<CardDescription>{GITHUB_CONFIG.TEXT.DESCRIPTION}</CardDescription>
72+
</CardHeader>
73+
<CardContent className="flex-1">
74+
<div className="grid w-full items-center gap-4">
75+
<div className="flex flex-col space-y-1.5">
76+
<Label htmlFor="github-repo-url">
77+
{GITHUB_CONFIG.TEXT.INPUT_LABEL}
78+
</Label>
79+
<Input
80+
id="github-repo-url"
81+
placeholder={GITHUB_CONFIG.TEXT.INPUT_PLACEHOLDER}
82+
value={repoUrl}
83+
onChange={(e) => setRepoUrl(e.target.value)}
84+
/>
85+
</div>
86+
87+
{status.message && (
88+
<div
89+
className={`flex items-center gap-2 text-sm p-3 rounded-md ${
90+
status.type === "success"
91+
? "bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400"
92+
: "bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400"
93+
}`}
94+
>
95+
{status.type === "success" ? (
96+
<CheckCircle2 className="h-4 w-4 shrink-0" />
97+
) : (
98+
<AlertCircle className="h-4 w-4 shrink-0" />
99+
)}
100+
<span>{status.message}</span>
101+
</div>
102+
)}
103+
</div>
104+
</CardContent>
105+
<CardFooter className="flex flex-col items-start gap-4 border-t pt-6">
106+
<Button className="w-full" onClick={handleConnect} disabled={isLoading}>
107+
{isLoading ? (
108+
<>
109+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
110+
{GITHUB_CONFIG.TEXT.BUTTON_CONNECTING}
111+
</>
112+
) : (
113+
GITHUB_CONFIG.TEXT.BUTTON_CONNECT
114+
)}
115+
</Button>
116+
<p className="text-xs text-muted-foreground">
117+
{GITHUB_CONFIG.TEXT.FOOTER_NOTE}
118+
</p>
119+
</CardFooter>
120+
</Card>
121+
);
122+
}

apps/web/config/env.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const ENV = {
2+
API_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1",
3+
} as const;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const GITHUB_CONFIG = {
2+
API_ENDPOINTS: {
3+
CONNECT: "/connect/github",
4+
},
5+
TEXT: {
6+
TITLE: "GitHub",
7+
DESCRIPTION: "Connect repositories to ingest code and commit history.",
8+
INPUT_LABEL: "Repository URL",
9+
INPUT_PLACEHOLDER: "https://github.com/owner/repo",
10+
BUTTON_CONNECT: "Connect Repository",
11+
BUTTON_CONNECTING: "Connecting...",
12+
SUCCESS_MESSAGE: (repoName: string) =>
13+
`Successfully connected to ${repoName}`,
14+
ERROR_EMPTY_URL: "Please enter a repository URL",
15+
ERROR_GENERIC: "Failed to connect repository",
16+
FOOTER_NOTE:
17+
"For live updates, please configure the GitHub App webhook in your repository settings.",
18+
},
19+
} as const;

0 commit comments

Comments
 (0)