KrishnaCosmic commited on
Commit
bde1b29
Β·
1 Parent(s): ead2eda

Fix missing await for async AI service calls

Browse files
src/app/api/profile/[username]/connect-repo/route.ts ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Connect Repository Route
3
+ *
4
+ * POST /api/profile/{userId}/connect-repo
5
+ * Connect a GitHub repo for monitoring and import its issues
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from "next/server";
9
+ import { getCurrentUser } from "@/lib/auth";
10
+ import { db } from "@/db";
11
+ import { repositories, issues, users } from "@/db/schema";
12
+ import { eq, and } from "drizzle-orm";
13
+ import { generateId, now } from "@/lib/utils";
14
+ import { createGitHubClient, fetchRepository } from "@/lib/github-client";
15
+
16
+ export async function POST(
17
+ request: NextRequest,
18
+ context: { params: Promise<{ username: string }> }
19
+ ) {
20
+ try {
21
+ const user = await getCurrentUser(request);
22
+ if (!user) {
23
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
24
+ }
25
+
26
+ const { username: userId } = await context.params;
27
+
28
+ // Verify user owns this profile
29
+ if (user.id !== userId) {
30
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
31
+ }
32
+
33
+ const body = await request.json();
34
+ const { repo_name } = body;
35
+
36
+ if (!repo_name) {
37
+ return NextResponse.json({ error: "repo_name is required" }, { status: 400 });
38
+ }
39
+
40
+ // Parse owner/repo from full name
41
+ const parts = repo_name.split('/');
42
+ const owner = parts.length > 1 ? parts[0] : user.username;
43
+ const repoName = parts.length > 1 ? parts[1] : parts[0];
44
+ const fullName = `${owner}/${repoName}`;
45
+
46
+ // Check if already connected
47
+ const existing = await db.select()
48
+ .from(repositories)
49
+ .where(and(
50
+ eq(repositories.name, fullName),
51
+ eq(repositories.userId, userId)
52
+ ))
53
+ .limit(1);
54
+
55
+ if (existing.length > 0) {
56
+ return NextResponse.json({
57
+ message: "Repository already connected",
58
+ repository: existing[0]
59
+ });
60
+ }
61
+
62
+ // Fetch repo info from GitHub
63
+ let githubRepoId = null;
64
+ if (user.githubAccessToken) {
65
+ try {
66
+ const octokit = createGitHubClient(user.githubAccessToken);
67
+ const repoData = await fetchRepository(octokit, owner, repoName);
68
+ githubRepoId = String(repoData.id);
69
+ } catch (e) {
70
+ console.warn("Could not fetch repo from GitHub:", e);
71
+ }
72
+ }
73
+
74
+ // Create repository record
75
+ const newRepo = {
76
+ id: generateId(),
77
+ githubRepoId: githubRepoId ? Number(githubRepoId) : Date.now(),
78
+ name: fullName,
79
+ owner,
80
+ userId,
81
+ createdAt: now(),
82
+ };
83
+
84
+ await db.insert(repositories).values(newRepo);
85
+
86
+ // Fetch and import issues from the repo
87
+ if (user.githubAccessToken) {
88
+ try {
89
+ const octokit = createGitHubClient(user.githubAccessToken);
90
+
91
+ // Fetch open issues
92
+ const { data: ghIssues } = await octokit.issues.listForRepo({
93
+ owner,
94
+ repo: repoName,
95
+ state: 'open',
96
+ per_page: 50,
97
+ });
98
+
99
+ // Import each issue
100
+ for (const ghIssue of ghIssues) {
101
+ // Skip PRs in issues list
102
+ if (ghIssue.pull_request) continue;
103
+
104
+ const existingIssue = await db.select()
105
+ .from(issues)
106
+ .where(eq(issues.githubIssueId, ghIssue.id))
107
+ .limit(1);
108
+
109
+ if (existingIssue.length === 0) {
110
+ await db.insert(issues).values({
111
+ id: generateId(),
112
+ githubIssueId: ghIssue.id,
113
+ number: ghIssue.number,
114
+ title: ghIssue.title,
115
+ body: ghIssue.body || '',
116
+ authorName: ghIssue.user?.login || 'unknown',
117
+ repoId: newRepo.id,
118
+ repoName: fullName,
119
+ owner,
120
+ repo: repoName,
121
+ htmlUrl: ghIssue.html_url,
122
+ state: ghIssue.state,
123
+ isPR: false,
124
+ createdAt: now(),
125
+ });
126
+ }
127
+ }
128
+
129
+ // Fetch open PRs
130
+ const { data: ghPRs } = await octokit.pulls.list({
131
+ owner,
132
+ repo: repoName,
133
+ state: 'open',
134
+ per_page: 50,
135
+ });
136
+
137
+ for (const ghPR of ghPRs) {
138
+ const existingPR = await db.select()
139
+ .from(issues)
140
+ .where(eq(issues.githubIssueId, ghPR.id))
141
+ .limit(1);
142
+
143
+ if (existingPR.length === 0) {
144
+ await db.insert(issues).values({
145
+ id: generateId(),
146
+ githubIssueId: ghPR.id,
147
+ number: ghPR.number,
148
+ title: ghPR.title,
149
+ body: ghPR.body || '',
150
+ authorName: ghPR.user?.login || 'unknown',
151
+ repoId: newRepo.id,
152
+ repoName: fullName,
153
+ owner,
154
+ repo: repoName,
155
+ htmlUrl: ghPR.html_url,
156
+ state: ghPR.state,
157
+ isPR: true,
158
+ createdAt: now(),
159
+ });
160
+ }
161
+ }
162
+
163
+ console.log(`Imported issues and PRs from ${fullName}`);
164
+ } catch (e) {
165
+ console.error("Error importing issues:", e);
166
+ // Don't fail the request, repo is still connected
167
+ }
168
+ }
169
+
170
+ return NextResponse.json({
171
+ message: "Repository connected successfully",
172
+ repository: newRepo
173
+ }, { status: 201 });
174
+
175
+ } catch (error) {
176
+ console.error("POST /api/profile/:id/connect-repo error:", error);
177
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
178
+ }
179
+ }
src/app/api/profile/[username]/connected-repos/route.ts CHANGED
@@ -1,6 +1,13 @@
 
 
 
 
 
 
 
1
  import { NextRequest, NextResponse } from "next/server";
2
  import { db } from "@/db";
3
- import { profileConnectedRepos } from "@/db/schema";
4
  import { eq } from "drizzle-orm";
5
 
6
  export async function GET(
@@ -8,14 +15,17 @@ export async function GET(
8
  context: { params: Promise<{ username: string }> }
9
  ) {
10
  try {
11
- const { username: id } = await context.params;
12
- // 'id' here corresponds to the profile's userId (since userId is PK for profiles)
13
- const repos = await db
14
- .select()
15
- .from(profileConnectedRepos)
16
- .where(eq(profileConnectedRepos.profileId, id));
17
 
18
- return NextResponse.json(repos);
 
 
 
19
  } catch (error) {
20
  console.error("GET /api/profile/:id/connected-repos error:", error);
21
  return NextResponse.json({ error: "Internal server error" }, { status: 500 });
 
1
+ /**
2
+ * Connected Repositories Route
3
+ *
4
+ * GET /api/profile/{userId}/connected-repos
5
+ * Get user's connected repositories
6
+ */
7
+
8
  import { NextRequest, NextResponse } from "next/server";
9
  import { db } from "@/db";
10
+ import { repositories } from "@/db/schema";
11
  import { eq } from "drizzle-orm";
12
 
13
  export async function GET(
 
15
  context: { params: Promise<{ username: string }> }
16
  ) {
17
  try {
18
+ const { username: userId } = await context.params;
19
+
20
+ // Fetch repositories connected by this user
21
+ const repos = await db.select()
22
+ .from(repositories)
23
+ .where(eq(repositories.userId, userId));
24
 
25
+ // Return list of repo names for simple display
26
+ return NextResponse.json({
27
+ repos: repos.map(r => r.name)
28
+ });
29
  } catch (error) {
30
  console.error("GET /api/profile/:id/connected-repos error:", error);
31
  return NextResponse.json({ error: "Internal server error" }, { status: 500 });
src/app/api/profile/[username]/disconnect-repo/route.ts ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Disconnect Repository Route
3
+ *
4
+ * DELETE /api/profile/{userId}/disconnect-repo
5
+ * Disconnect a repository
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from "next/server";
9
+ import { getCurrentUser } from "@/lib/auth";
10
+ import { db } from "@/db";
11
+ import { repositories } from "@/db/schema";
12
+ import { eq, and } from "drizzle-orm";
13
+
14
+ export async function DELETE(
15
+ request: NextRequest,
16
+ context: { params: Promise<{ username: string }> }
17
+ ) {
18
+ try {
19
+ const user = await getCurrentUser(request);
20
+ if (!user) {
21
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
22
+ }
23
+
24
+ const { username: userId } = await context.params;
25
+ const { searchParams } = new URL(request.url);
26
+ const repoName = searchParams.get("repo_name");
27
+
28
+ if (!repoName) {
29
+ return NextResponse.json({ error: "repo_name is required" }, { status: 400 });
30
+ }
31
+
32
+ // Verify user owns this profile
33
+ if (user.id !== userId) {
34
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
35
+ }
36
+
37
+ // Delete the repository
38
+ await db.delete(repositories)
39
+ .where(and(
40
+ eq(repositories.name, repoName),
41
+ eq(repositories.userId, userId)
42
+ ));
43
+
44
+ return NextResponse.json({
45
+ message: "Repository disconnected successfully"
46
+ });
47
+
48
+ } catch (error) {
49
+ console.error("DELETE /api/profile/:id/disconnect-repo error:", error);
50
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
51
+ }
52
+ }
src/app/api/profile/[username]/repos/route.ts CHANGED
@@ -1,7 +1,15 @@
 
 
 
 
 
 
 
1
  import { NextRequest, NextResponse } from "next/server";
2
  import { db } from "@/db";
3
- import { repositories, users } from "@/db/schema";
4
- import { eq, desc } from "drizzle-orm";
 
5
 
6
  export async function GET(
7
  request: NextRequest,
@@ -10,20 +18,38 @@ export async function GET(
10
  try {
11
  const { username } = await context.params;
12
 
13
- // Find user first to get ID
14
  const user = await db.select().from(users).where(eq(users.username, username)).limit(1);
15
 
16
  if (!user[0]) {
17
- return NextResponse.json({ error: "User not found" }, { status: 404 });
18
  }
19
 
20
- const repos = await db
21
- .select()
22
- .from(repositories)
23
- .where(eq(repositories.userId, user[0].id))
24
- .orderBy(desc(repositories.createdAt));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- return NextResponse.json(repos);
27
  } catch (error) {
28
  console.error("GET /api/profile/:username/repos error:", error);
29
  return NextResponse.json({ error: "Internal server error" }, { status: 500 });
 
1
+ /**
2
+ * User Repositories Route
3
+ *
4
+ * GET /api/profile/{username}/repos
5
+ * Fetch user's GitHub repositories
6
+ */
7
+
8
  import { NextRequest, NextResponse } from "next/server";
9
  import { db } from "@/db";
10
+ import { users } from "@/db/schema";
11
+ import { eq } from "drizzle-orm";
12
+ import { createGitHubClient, fetchAllUserRepositories } from "@/lib/github-client";
13
 
14
  export async function GET(
15
  request: NextRequest,
 
18
  try {
19
  const { username } = await context.params;
20
 
21
+ // Find user to get their GitHub token
22
  const user = await db.select().from(users).where(eq(users.username, username)).limit(1);
23
 
24
  if (!user[0]) {
25
+ return NextResponse.json({ repos: [] });
26
  }
27
 
28
+ // If user has a GitHub token, fetch their repos from GitHub
29
+ if (user[0].githubAccessToken) {
30
+ try {
31
+ const octokit = createGitHubClient(user[0].githubAccessToken);
32
+ const repos = await fetchAllUserRepositories(octokit, username);
33
+
34
+ return NextResponse.json({
35
+ repos: repos.map(repo => ({
36
+ name: repo.full_name,
37
+ description: repo.description,
38
+ language: repo.language,
39
+ stars: repo.stargazers_count,
40
+ forks: repo.forks_count,
41
+ url: repo.html_url,
42
+ private: repo.private,
43
+ updatedAt: repo.updated_at,
44
+ }))
45
+ });
46
+ } catch (githubError) {
47
+ console.error("GitHub API error:", githubError);
48
+ // Fall through to return empty on error
49
+ }
50
+ }
51
 
52
+ return NextResponse.json({ repos: [] });
53
  } catch (error) {
54
  console.error("GET /api/profile/:username/repos error:", error);
55
  return NextResponse.json({ error: "Internal server error" }, { status: 500 });
src/app/api/spark/badges/user/[username]/route.ts CHANGED
@@ -1,5 +1,28 @@
 
 
 
 
 
 
 
1
  import { NextRequest, NextResponse } from "next/server";
2
- import { getUserBadges } from "@/lib/db/queries/gamification";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  export async function GET(
5
  request: NextRequest,
@@ -7,8 +30,35 @@ export async function GET(
7
  ) {
8
  try {
9
  const { username } = await context.params;
10
- const badges = await getUserBadges(username);
11
- return NextResponse.json(badges);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  } catch (error) {
13
  console.error("GET /api/spark/badges/user/:username error:", error);
14
  return NextResponse.json({ error: "Internal server error" }, { status: 500 });
 
1
+ /**
2
+ * User Badges Route
3
+ *
4
+ * GET /api/spark/badges/user/{username}
5
+ * Fetch user's badges/achievements
6
+ */
7
+
8
  import { NextRequest, NextResponse } from "next/server";
9
+ import { db } from "@/db";
10
+ import { trophies, users } from "@/db/schema";
11
+ import { eq, desc } from "drizzle-orm";
12
+
13
+ // Badge definitions - these are the possible badges a user can earn
14
+ const BADGE_DEFINITIONS = [
15
+ { id: 'first_pr', name: 'First Pull Request', description: 'Opened your first pull request', icon: '🎯', category: 'contribution' },
16
+ { id: 'first_issue', name: 'First Issue', description: 'Created your first issue', icon: 'πŸ“', category: 'contribution' },
17
+ { id: 'merged_pr', name: 'PR Merged', description: 'Had a pull request merged', icon: 'πŸŽ‰', category: 'contribution' },
18
+ { id: 'streak_7', name: '7 Day Streak', description: 'Contributed for 7 days in a row', icon: 'πŸ”₯', category: 'streak' },
19
+ { id: 'streak_30', name: '30 Day Streak', description: 'Contributed for 30 days in a row', icon: 'πŸ’ͺ', category: 'streak' },
20
+ { id: 'contributor_10', name: '10 Contributions', description: 'Made 10 contributions', icon: '⭐', category: 'milestone' },
21
+ { id: 'contributor_50', name: '50 Contributions', description: 'Made 50 contributions', icon: '🌟', category: 'milestone' },
22
+ { id: 'contributor_100', name: '100 Contributions', description: 'Made 100 contributions', icon: 'πŸ’―', category: 'milestone' },
23
+ { id: 'mentor', name: 'Mentor', description: 'Helped another contributor', icon: 'πŸ§‘β€πŸ«', category: 'community' },
24
+ { id: 'reviewer', name: 'Code Reviewer', description: 'Reviewed pull requests', icon: 'πŸ‘€', category: 'contribution' },
25
+ ];
26
 
27
  export async function GET(
28
  request: NextRequest,
 
30
  ) {
31
  try {
32
  const { username } = await context.params;
33
+
34
+ // Get user
35
+ const user = await db.select().from(users).where(eq(users.username, username)).limit(1);
36
+ if (!user[0]) {
37
+ return NextResponse.json({ all_badges: BADGE_DEFINITIONS, earned_badges: [] });
38
+ }
39
+
40
+ // Get earned trophies/badges from database
41
+ const earnedTrophies = await db.select()
42
+ .from(trophies)
43
+ .where(eq(trophies.userId, user[0].id))
44
+ .orderBy(desc(trophies.awardedAt));
45
+
46
+ // Map earned trophies to badge IDs
47
+ const earnedBadgeIds = new Set(earnedTrophies.map(t => t.trophyType));
48
+
49
+ // Mark badges as earned/not earned
50
+ const allBadges = BADGE_DEFINITIONS.map(badge => ({
51
+ ...badge,
52
+ earned: earnedBadgeIds.has(badge.id),
53
+ awardedAt: earnedTrophies.find(t => t.trophyType === badge.id)?.awardedAt || null,
54
+ }));
55
+
56
+ return NextResponse.json({
57
+ all_badges: allBadges,
58
+ earned_badges: allBadges.filter(b => b.earned),
59
+ total_earned: earnedTrophies.length,
60
+ total_possible: BADGE_DEFINITIONS.length,
61
+ });
62
  } catch (error) {
63
  console.error("GET /api/spark/badges/user/:username error:", error);
64
  return NextResponse.json({ error: "Internal server error" }, { status: 500 });