@agent-relay/cloud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/admin.d.ts +8 -0
- package/dist/api/admin.d.ts.map +1 -0
- package/dist/api/admin.js +225 -0
- package/dist/api/admin.js.map +1 -0
- package/dist/api/auth.d.ts +20 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +136 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/billing.d.ts +7 -0
- package/dist/api/billing.d.ts.map +1 -0
- package/dist/api/billing.js +564 -0
- package/dist/api/billing.js.map +1 -0
- package/dist/api/cli-pty-runner.d.ts +53 -0
- package/dist/api/cli-pty-runner.d.ts.map +1 -0
- package/dist/api/cli-pty-runner.js +193 -0
- package/dist/api/cli-pty-runner.js.map +1 -0
- package/dist/api/codex-auth-helper.d.ts +21 -0
- package/dist/api/codex-auth-helper.d.ts.map +1 -0
- package/dist/api/codex-auth-helper.js +327 -0
- package/dist/api/codex-auth-helper.js.map +1 -0
- package/dist/api/consensus.d.ts +13 -0
- package/dist/api/consensus.d.ts.map +1 -0
- package/dist/api/consensus.js +261 -0
- package/dist/api/consensus.js.map +1 -0
- package/dist/api/coordinators.d.ts +8 -0
- package/dist/api/coordinators.d.ts.map +1 -0
- package/dist/api/coordinators.js +750 -0
- package/dist/api/coordinators.js.map +1 -0
- package/dist/api/daemons.d.ts +12 -0
- package/dist/api/daemons.d.ts.map +1 -0
- package/dist/api/daemons.js +535 -0
- package/dist/api/daemons.js.map +1 -0
- package/dist/api/generic-webhooks.d.ts +8 -0
- package/dist/api/generic-webhooks.d.ts.map +1 -0
- package/dist/api/generic-webhooks.js +129 -0
- package/dist/api/generic-webhooks.js.map +1 -0
- package/dist/api/git.d.ts +8 -0
- package/dist/api/git.d.ts.map +1 -0
- package/dist/api/git.js +269 -0
- package/dist/api/git.js.map +1 -0
- package/dist/api/github-app.d.ts +11 -0
- package/dist/api/github-app.d.ts.map +1 -0
- package/dist/api/github-app.js +223 -0
- package/dist/api/github-app.js.map +1 -0
- package/dist/api/middleware/planLimits.d.ts +43 -0
- package/dist/api/middleware/planLimits.d.ts.map +1 -0
- package/dist/api/middleware/planLimits.js +202 -0
- package/dist/api/middleware/planLimits.js.map +1 -0
- package/dist/api/monitoring.d.ts +11 -0
- package/dist/api/monitoring.d.ts.map +1 -0
- package/dist/api/monitoring.js +578 -0
- package/dist/api/monitoring.js.map +1 -0
- package/dist/api/nango-auth.d.ts +9 -0
- package/dist/api/nango-auth.d.ts.map +1 -0
- package/dist/api/nango-auth.js +674 -0
- package/dist/api/nango-auth.js.map +1 -0
- package/dist/api/onboarding.d.ts +15 -0
- package/dist/api/onboarding.d.ts.map +1 -0
- package/dist/api/onboarding.js +679 -0
- package/dist/api/onboarding.js.map +1 -0
- package/dist/api/policy.d.ts +8 -0
- package/dist/api/policy.d.ts.map +1 -0
- package/dist/api/policy.js +229 -0
- package/dist/api/policy.js.map +1 -0
- package/dist/api/provider-env.d.ts +14 -0
- package/dist/api/provider-env.d.ts.map +1 -0
- package/dist/api/provider-env.js +75 -0
- package/dist/api/provider-env.js.map +1 -0
- package/dist/api/providers.d.ts +7 -0
- package/dist/api/providers.d.ts.map +1 -0
- package/dist/api/providers.js +564 -0
- package/dist/api/providers.js.map +1 -0
- package/dist/api/repos.d.ts +8 -0
- package/dist/api/repos.d.ts.map +1 -0
- package/dist/api/repos.js +577 -0
- package/dist/api/repos.js.map +1 -0
- package/dist/api/sessions.d.ts +11 -0
- package/dist/api/sessions.d.ts.map +1 -0
- package/dist/api/sessions.js +302 -0
- package/dist/api/sessions.js.map +1 -0
- package/dist/api/teams.d.ts +7 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +281 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/test-helpers.d.ts +10 -0
- package/dist/api/test-helpers.d.ts.map +1 -0
- package/dist/api/test-helpers.js +745 -0
- package/dist/api/test-helpers.js.map +1 -0
- package/dist/api/usage.d.ts +7 -0
- package/dist/api/usage.d.ts.map +1 -0
- package/dist/api/usage.js +111 -0
- package/dist/api/usage.js.map +1 -0
- package/dist/api/webhooks.d.ts +8 -0
- package/dist/api/webhooks.d.ts.map +1 -0
- package/dist/api/webhooks.js +645 -0
- package/dist/api/webhooks.js.map +1 -0
- package/dist/api/workspaces.d.ts +25 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +1799 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/billing/index.d.ts +9 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +9 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/plans.d.ts +39 -0
- package/dist/billing/plans.d.ts.map +1 -0
- package/dist/billing/plans.js +245 -0
- package/dist/billing/plans.js.map +1 -0
- package/dist/billing/service.d.ts +80 -0
- package/dist/billing/service.d.ts.map +1 -0
- package/dist/billing/service.js +388 -0
- package/dist/billing/service.js.map +1 -0
- package/dist/billing/types.d.ts +141 -0
- package/dist/billing/types.d.ts.map +1 -0
- package/dist/billing/types.js +7 -0
- package/dist/billing/types.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/db/bulk-ingest.d.ts +89 -0
- package/dist/db/bulk-ingest.d.ts.map +1 -0
- package/dist/db/bulk-ingest.js +268 -0
- package/dist/db/bulk-ingest.js.map +1 -0
- package/dist/db/drizzle.d.ts +256 -0
- package/dist/db/drizzle.d.ts.map +1 -0
- package/dist/db/drizzle.js +1286 -0
- package/dist/db/drizzle.js.map +1 -0
- package/dist/db/index.d.ts +55 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +68 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +4873 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +620 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/provisioner/index.d.ts +207 -0
- package/dist/provisioner/index.d.ts.map +1 -0
- package/dist/provisioner/index.js +2114 -0
- package/dist/provisioner/index.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1924 -0
- package/dist/server.js.map +1 -0
- package/dist/services/auto-scaler.d.ts +152 -0
- package/dist/services/auto-scaler.d.ts.map +1 -0
- package/dist/services/auto-scaler.js +439 -0
- package/dist/services/auto-scaler.js.map +1 -0
- package/dist/services/capacity-manager.d.ts +148 -0
- package/dist/services/capacity-manager.d.ts.map +1 -0
- package/dist/services/capacity-manager.js +449 -0
- package/dist/services/capacity-manager.js.map +1 -0
- package/dist/services/ci-agent-spawner.d.ts +49 -0
- package/dist/services/ci-agent-spawner.d.ts.map +1 -0
- package/dist/services/ci-agent-spawner.js +373 -0
- package/dist/services/ci-agent-spawner.js.map +1 -0
- package/dist/services/cloud-message-bus.d.ts +28 -0
- package/dist/services/cloud-message-bus.d.ts.map +1 -0
- package/dist/services/cloud-message-bus.js +19 -0
- package/dist/services/cloud-message-bus.js.map +1 -0
- package/dist/services/compute-enforcement.d.ts +57 -0
- package/dist/services/compute-enforcement.d.ts.map +1 -0
- package/dist/services/compute-enforcement.js +175 -0
- package/dist/services/compute-enforcement.js.map +1 -0
- package/dist/services/coordinator.d.ts +62 -0
- package/dist/services/coordinator.d.ts.map +1 -0
- package/dist/services/coordinator.js +389 -0
- package/dist/services/coordinator.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +25 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/intro-expiration.d.ts +60 -0
- package/dist/services/intro-expiration.d.ts.map +1 -0
- package/dist/services/intro-expiration.js +252 -0
- package/dist/services/intro-expiration.js.map +1 -0
- package/dist/services/mention-handler.d.ts +65 -0
- package/dist/services/mention-handler.d.ts.map +1 -0
- package/dist/services/mention-handler.js +405 -0
- package/dist/services/mention-handler.js.map +1 -0
- package/dist/services/nango.d.ts +201 -0
- package/dist/services/nango.d.ts.map +1 -0
- package/dist/services/nango.js +392 -0
- package/dist/services/nango.js.map +1 -0
- package/dist/services/persistence.d.ts +131 -0
- package/dist/services/persistence.d.ts.map +1 -0
- package/dist/services/persistence.js +200 -0
- package/dist/services/persistence.js.map +1 -0
- package/dist/services/planLimits.d.ts +147 -0
- package/dist/services/planLimits.d.ts.map +1 -0
- package/dist/services/planLimits.js +335 -0
- package/dist/services/planLimits.js.map +1 -0
- package/dist/services/presence-registry.d.ts +56 -0
- package/dist/services/presence-registry.d.ts.map +1 -0
- package/dist/services/presence-registry.js +91 -0
- package/dist/services/presence-registry.js.map +1 -0
- package/dist/services/scaling-orchestrator.d.ts +159 -0
- package/dist/services/scaling-orchestrator.d.ts.map +1 -0
- package/dist/services/scaling-orchestrator.js +502 -0
- package/dist/services/scaling-orchestrator.js.map +1 -0
- package/dist/services/scaling-policy.d.ts +121 -0
- package/dist/services/scaling-policy.d.ts.map +1 -0
- package/dist/services/scaling-policy.js +415 -0
- package/dist/services/scaling-policy.js.map +1 -0
- package/dist/services/ssh-security.d.ts +31 -0
- package/dist/services/ssh-security.d.ts.map +1 -0
- package/dist/services/ssh-security.js +63 -0
- package/dist/services/ssh-security.js.map +1 -0
- package/dist/services/workspace-keepalive.d.ts +76 -0
- package/dist/services/workspace-keepalive.d.ts.map +1 -0
- package/dist/services/workspace-keepalive.js +234 -0
- package/dist/services/workspace-keepalive.js.map +1 -0
- package/dist/shims/consensus.d.ts +23 -0
- package/dist/shims/consensus.d.ts.map +1 -0
- package/dist/shims/consensus.js +5 -0
- package/dist/shims/consensus.js.map +1 -0
- package/dist/webhooks/index.d.ts +24 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +29 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/parsers/github.d.ts +8 -0
- package/dist/webhooks/parsers/github.d.ts.map +1 -0
- package/dist/webhooks/parsers/github.js +234 -0
- package/dist/webhooks/parsers/github.js.map +1 -0
- package/dist/webhooks/parsers/index.d.ts +23 -0
- package/dist/webhooks/parsers/index.d.ts.map +1 -0
- package/dist/webhooks/parsers/index.js +30 -0
- package/dist/webhooks/parsers/index.js.map +1 -0
- package/dist/webhooks/parsers/linear.d.ts +9 -0
- package/dist/webhooks/parsers/linear.d.ts.map +1 -0
- package/dist/webhooks/parsers/linear.js +258 -0
- package/dist/webhooks/parsers/linear.js.map +1 -0
- package/dist/webhooks/parsers/slack.d.ts +9 -0
- package/dist/webhooks/parsers/slack.d.ts.map +1 -0
- package/dist/webhooks/parsers/slack.js +214 -0
- package/dist/webhooks/parsers/slack.js.map +1 -0
- package/dist/webhooks/responders/github.d.ts +8 -0
- package/dist/webhooks/responders/github.d.ts.map +1 -0
- package/dist/webhooks/responders/github.js +73 -0
- package/dist/webhooks/responders/github.js.map +1 -0
- package/dist/webhooks/responders/index.d.ts +23 -0
- package/dist/webhooks/responders/index.d.ts.map +1 -0
- package/dist/webhooks/responders/index.js +30 -0
- package/dist/webhooks/responders/index.js.map +1 -0
- package/dist/webhooks/responders/linear.d.ts +9 -0
- package/dist/webhooks/responders/linear.d.ts.map +1 -0
- package/dist/webhooks/responders/linear.js +149 -0
- package/dist/webhooks/responders/linear.js.map +1 -0
- package/dist/webhooks/responders/slack.d.ts +20 -0
- package/dist/webhooks/responders/slack.d.ts.map +1 -0
- package/dist/webhooks/responders/slack.js +178 -0
- package/dist/webhooks/responders/slack.js.map +1 -0
- package/dist/webhooks/router.d.ts +25 -0
- package/dist/webhooks/router.d.ts.map +1 -0
- package/dist/webhooks/router.js +504 -0
- package/dist/webhooks/router.js.map +1 -0
- package/dist/webhooks/rules-engine.d.ts +24 -0
- package/dist/webhooks/rules-engine.d.ts.map +1 -0
- package/dist/webhooks/rules-engine.js +287 -0
- package/dist/webhooks/rules-engine.js.map +1 -0
- package/dist/webhooks/types.d.ts +186 -0
- package/dist/webhooks/types.d.ts.map +1 -0
- package/dist/webhooks/types.js +8 -0
- package/dist/webhooks/types.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nango Auth API Routes
|
|
3
|
+
*
|
|
4
|
+
* Handles GitHub OAuth via Nango with two-connection pattern:
|
|
5
|
+
* - github: User login (identity)
|
|
6
|
+
* - github-app-oauth: Repository access
|
|
7
|
+
*/
|
|
8
|
+
import { Router } from 'express';
|
|
9
|
+
import { randomUUID } from 'crypto';
|
|
10
|
+
import { requireAuth } from './auth.js';
|
|
11
|
+
import { db } from '../db/index.js';
|
|
12
|
+
import { nangoService, NANGO_INTEGRATIONS } from '../services/nango.js';
|
|
13
|
+
export const nangoAuthRouter = Router();
|
|
14
|
+
/**
|
|
15
|
+
* GET /api/auth/nango/status
|
|
16
|
+
* Check if Nango is configured
|
|
17
|
+
*/
|
|
18
|
+
nangoAuthRouter.get('/status', (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
res.json({
|
|
21
|
+
configured: true,
|
|
22
|
+
integrations: NANGO_INTEGRATIONS,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (_error) {
|
|
26
|
+
res.json({
|
|
27
|
+
configured: false,
|
|
28
|
+
message: 'Nango not configured',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* GET /api/auth/nango/login-session
|
|
34
|
+
* Create a Nango connect session for GitHub login
|
|
35
|
+
*/
|
|
36
|
+
nangoAuthRouter.get('/login-session', async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const tempUserId = randomUUID();
|
|
39
|
+
const session = await nangoService.createConnectSession([NANGO_INTEGRATIONS.GITHUB_USER], { id: tempUserId });
|
|
40
|
+
res.json({ sessionToken: session.token, tempUserId });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error('Error creating login session:', error);
|
|
44
|
+
res.status(500).json({ error: 'Failed to create login session' });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* GET /api/auth/nango/login-status/:connectionId
|
|
49
|
+
* Poll for login completion after Nango connect UI
|
|
50
|
+
*/
|
|
51
|
+
nangoAuthRouter.get('/login-status/:connectionId', async (req, res) => {
|
|
52
|
+
const connectionId = req.params.connectionId;
|
|
53
|
+
try {
|
|
54
|
+
// Check if a user exists with this incoming connection
|
|
55
|
+
const user = await db.users.findByIncomingConnectionId(connectionId);
|
|
56
|
+
if (!user) {
|
|
57
|
+
return res.json({ ready: false });
|
|
58
|
+
}
|
|
59
|
+
// Issue session
|
|
60
|
+
req.session.userId = user.id;
|
|
61
|
+
// Clear incoming connection ID
|
|
62
|
+
await db.users.clearIncomingConnectionId(user.id);
|
|
63
|
+
// Check if user has any repos connected
|
|
64
|
+
const repos = await db.repositories.findByUserId(user.id);
|
|
65
|
+
const hasRepos = repos.length > 0;
|
|
66
|
+
res.json({
|
|
67
|
+
ready: true,
|
|
68
|
+
hasRepos,
|
|
69
|
+
user: {
|
|
70
|
+
id: user.id,
|
|
71
|
+
githubUsername: user.githubUsername,
|
|
72
|
+
email: user.email,
|
|
73
|
+
avatarUrl: user.avatarUrl,
|
|
74
|
+
plan: user.plan,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('Error checking login status:', error);
|
|
80
|
+
res.status(500).json({ error: 'Failed to check login status' });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* GET /api/auth/nango/repo-session
|
|
85
|
+
* Create a Nango connect session for GitHub App OAuth (repo access)
|
|
86
|
+
* Requires authentication
|
|
87
|
+
*/
|
|
88
|
+
nangoAuthRouter.get('/repo-session', requireAuth, async (req, res) => {
|
|
89
|
+
const userId = req.session.userId;
|
|
90
|
+
try {
|
|
91
|
+
const user = await db.users.findById(userId);
|
|
92
|
+
if (!user) {
|
|
93
|
+
return res.status(404).json({ error: 'User not found' });
|
|
94
|
+
}
|
|
95
|
+
const session = await nangoService.createConnectSession([NANGO_INTEGRATIONS.GITHUB_APP], { id: user.id, email: user.email || undefined });
|
|
96
|
+
res.json({ sessionToken: session.token });
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Error creating repo session:', error);
|
|
100
|
+
res.status(500).json({ error: 'Failed to create repo session' });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* GET /api/auth/nango/repo-status/:connectionId
|
|
105
|
+
* Poll for repo sync completion after GitHub App OAuth
|
|
106
|
+
* Requires authentication
|
|
107
|
+
*/
|
|
108
|
+
nangoAuthRouter.get('/repo-status/:connectionId', requireAuth, async (req, res) => {
|
|
109
|
+
const userId = req.session.userId;
|
|
110
|
+
const _connectionId = req.params.connectionId;
|
|
111
|
+
try {
|
|
112
|
+
const user = await db.users.findById(userId);
|
|
113
|
+
if (!user) {
|
|
114
|
+
return res.status(404).json({ error: 'User not found' });
|
|
115
|
+
}
|
|
116
|
+
// Check for pending org approval
|
|
117
|
+
if (user.pendingInstallationRequest) {
|
|
118
|
+
return res.json({
|
|
119
|
+
ready: false,
|
|
120
|
+
pendingApproval: true,
|
|
121
|
+
message: 'Waiting for organization admin approval',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Check if repos have been synced
|
|
125
|
+
const repos = await db.repositories.findByUserId(userId);
|
|
126
|
+
const reposFromConnection = repos.filter(r => r.syncStatus === 'synced' && r.nangoConnectionId);
|
|
127
|
+
if (reposFromConnection.length === 0) {
|
|
128
|
+
return res.json({ ready: false });
|
|
129
|
+
}
|
|
130
|
+
// Check workspace status for frontend visibility
|
|
131
|
+
const workspaces = await db.workspaces.findByUserId(userId);
|
|
132
|
+
const primaryWorkspace = workspaces[0];
|
|
133
|
+
res.json({
|
|
134
|
+
ready: true,
|
|
135
|
+
repos: reposFromConnection.map(r => ({
|
|
136
|
+
id: r.id,
|
|
137
|
+
fullName: r.githubFullName,
|
|
138
|
+
isPrivate: r.isPrivate,
|
|
139
|
+
defaultBranch: r.defaultBranch,
|
|
140
|
+
})),
|
|
141
|
+
workspace: primaryWorkspace ? {
|
|
142
|
+
id: primaryWorkspace.id,
|
|
143
|
+
name: primaryWorkspace.name,
|
|
144
|
+
status: primaryWorkspace.status,
|
|
145
|
+
publicUrl: primaryWorkspace.publicUrl,
|
|
146
|
+
} : null,
|
|
147
|
+
workspaceProvisioning: primaryWorkspace?.status === 'provisioning',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('Error checking repo status:', error);
|
|
152
|
+
res.status(500).json({ error: 'Failed to check repo status' });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Nango Webhook Handler
|
|
157
|
+
// ============================================================================
|
|
158
|
+
/**
|
|
159
|
+
* POST /api/auth/nango/webhook
|
|
160
|
+
* Handle Nango webhooks for auth and sync events
|
|
161
|
+
*/
|
|
162
|
+
nangoAuthRouter.post('/webhook', async (req, res) => {
|
|
163
|
+
const rawBody = req.rawBody || JSON.stringify(req.body);
|
|
164
|
+
// Verify webhook signature if present
|
|
165
|
+
const hasSignature = req.headers['x-nango-signature'] || req.headers['x-nango-hmac-sha256'];
|
|
166
|
+
if (hasSignature) {
|
|
167
|
+
if (!nangoService.verifyWebhookSignature(rawBody, req.headers)) {
|
|
168
|
+
console.error('[nango-webhook] Invalid signature');
|
|
169
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const payload = req.body;
|
|
173
|
+
console.log(`[nango-webhook] Received ${payload.type} event`);
|
|
174
|
+
try {
|
|
175
|
+
switch (payload.type) {
|
|
176
|
+
case 'auth':
|
|
177
|
+
await handleAuthWebhook(payload);
|
|
178
|
+
break;
|
|
179
|
+
case 'sync':
|
|
180
|
+
console.log('[nango-webhook] Sync event received');
|
|
181
|
+
break;
|
|
182
|
+
case 'forward':
|
|
183
|
+
await handleForwardWebhook(payload);
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
console.log(`[nango-webhook] Unhandled event type: ${payload.type}`);
|
|
187
|
+
}
|
|
188
|
+
res.json({ success: true });
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error('[nango-webhook] Error processing webhook:', error);
|
|
192
|
+
res.status(500).json({ error: 'Failed to process webhook' });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
/**
|
|
196
|
+
* Handle Nango auth webhook
|
|
197
|
+
*/
|
|
198
|
+
async function handleAuthWebhook(payload) {
|
|
199
|
+
const { connectionId, providerConfigKey, endUser } = payload;
|
|
200
|
+
console.log(`[nango-webhook] Auth event for ${providerConfigKey} (${connectionId})`);
|
|
201
|
+
if (providerConfigKey === NANGO_INTEGRATIONS.GITHUB_USER) {
|
|
202
|
+
await handleLoginWebhook(connectionId, endUser);
|
|
203
|
+
}
|
|
204
|
+
else if (providerConfigKey === NANGO_INTEGRATIONS.GITHUB_APP) {
|
|
205
|
+
await handleRepoAuthWebhook(connectionId, endUser);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check user's repo access and auto-add them to workspaces
|
|
210
|
+
* Uses GitHub user OAuth to query accessible repos and persists them to database
|
|
211
|
+
*/
|
|
212
|
+
async function checkAndAutoAddToWorkspaces(userId, connectionId) {
|
|
213
|
+
try {
|
|
214
|
+
const user = await db.users.findById(userId);
|
|
215
|
+
if (!user)
|
|
216
|
+
return;
|
|
217
|
+
console.log(`[nango-webhook] Checking workspace auto-add for ${user.githubUsername}`);
|
|
218
|
+
// Query repos the user has access to via GitHub OAuth
|
|
219
|
+
const { repositories } = await nangoService.listUserAccessibleRepos(connectionId, {
|
|
220
|
+
perPage: 100,
|
|
221
|
+
type: 'all',
|
|
222
|
+
});
|
|
223
|
+
const workspacesToJoin = new Set();
|
|
224
|
+
// Check for workspace memberships - only persist repos that match existing workspaces
|
|
225
|
+
for (const repo of repositories) {
|
|
226
|
+
// Check if any user has this repo linked to a workspace
|
|
227
|
+
const allRepoRecords = await db.repositories.findByGithubFullName(repo.fullName);
|
|
228
|
+
let matchedWorkspaceId = null;
|
|
229
|
+
for (const record of allRepoRecords) {
|
|
230
|
+
if (record.workspaceId) {
|
|
231
|
+
workspacesToJoin.add(record.workspaceId);
|
|
232
|
+
matchedWorkspaceId = record.workspaceId; // Save the workspaceId to copy
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Only persist repos that are linked to workspaces
|
|
236
|
+
if (matchedWorkspaceId) {
|
|
237
|
+
await db.repositories.upsert({
|
|
238
|
+
userId: user.id,
|
|
239
|
+
githubFullName: repo.fullName,
|
|
240
|
+
githubId: repo.id,
|
|
241
|
+
isPrivate: repo.isPrivate,
|
|
242
|
+
defaultBranch: repo.defaultBranch,
|
|
243
|
+
nangoConnectionId: connectionId,
|
|
244
|
+
workspaceId: matchedWorkspaceId, // Copy the workspaceId
|
|
245
|
+
syncStatus: 'synced',
|
|
246
|
+
lastSyncedAt: new Date(),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Auto-add user to workspaces
|
|
251
|
+
for (const workspaceId of workspacesToJoin) {
|
|
252
|
+
const existingMembership = await db.workspaceMembers.findMembership(workspaceId, userId);
|
|
253
|
+
if (!existingMembership) {
|
|
254
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
255
|
+
if (workspace) {
|
|
256
|
+
console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
|
|
257
|
+
await db.workspaceMembers.addMember({
|
|
258
|
+
workspaceId,
|
|
259
|
+
userId,
|
|
260
|
+
role: 'member',
|
|
261
|
+
invitedBy: workspace.userId,
|
|
262
|
+
});
|
|
263
|
+
await db.workspaceMembers.acceptInvite(workspaceId, userId);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
console.log(`[nango-webhook] Synced ${repositories.length} repos, auto-added ${user.githubUsername} to ${workspacesToJoin.size} workspaces`);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.error(`[nango-webhook] Error checking workspace auto-add:`, error);
|
|
271
|
+
// Non-fatal - don't throw
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Handle GitHub login webhook
|
|
276
|
+
*
|
|
277
|
+
* Three scenarios:
|
|
278
|
+
* 1. New user - Create user record, keep connection as permanent
|
|
279
|
+
* 2. Returning user with existing connection - Store incoming ID for polling, delete temp connection
|
|
280
|
+
* 3. Existing user, first connection - Set connection ID as permanent
|
|
281
|
+
*/
|
|
282
|
+
async function handleLoginWebhook(connectionId, _endUser) {
|
|
283
|
+
// Get GitHub user info via Nango proxy
|
|
284
|
+
const githubUser = await nangoService.getGithubUser(connectionId);
|
|
285
|
+
const githubId = String(githubUser.id);
|
|
286
|
+
// Check if user already exists
|
|
287
|
+
const existingUser = await db.users.findByGithubId(githubId);
|
|
288
|
+
// SCENARIO 1: New user
|
|
289
|
+
if (!existingUser) {
|
|
290
|
+
const newUser = await db.users.upsert({
|
|
291
|
+
githubId,
|
|
292
|
+
githubUsername: githubUser.login,
|
|
293
|
+
email: githubUser.email || null,
|
|
294
|
+
avatarUrl: githubUser.avatar_url || null,
|
|
295
|
+
nangoConnectionId: connectionId,
|
|
296
|
+
incomingConnectionId: connectionId,
|
|
297
|
+
});
|
|
298
|
+
// Update connection with real user ID
|
|
299
|
+
await nangoService.updateEndUser(connectionId, NANGO_INTEGRATIONS.GITHUB_USER, {
|
|
300
|
+
id: newUser.id,
|
|
301
|
+
email: newUser.email || undefined,
|
|
302
|
+
});
|
|
303
|
+
console.log(`[nango-webhook] New user created: ${githubUser.login}`);
|
|
304
|
+
// Check for auto-add to workspaces based on repo access
|
|
305
|
+
await checkAndAutoAddToWorkspaces(newUser.id, connectionId);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// SCENARIO 2: Returning user with existing connection - delete temp connection
|
|
309
|
+
if (existingUser.nangoConnectionId && existingUser.nangoConnectionId !== connectionId) {
|
|
310
|
+
console.log(`[nango-webhook] Returning user: ${githubUser.login}`, {
|
|
311
|
+
permanentConnectionId: existingUser.nangoConnectionId,
|
|
312
|
+
incomingConnectionId: connectionId,
|
|
313
|
+
});
|
|
314
|
+
// Store incoming connection ID for polling
|
|
315
|
+
await db.users.update(existingUser.id, {
|
|
316
|
+
incomingConnectionId: connectionId,
|
|
317
|
+
githubUsername: githubUser.login,
|
|
318
|
+
avatarUrl: githubUser.avatar_url || null,
|
|
319
|
+
});
|
|
320
|
+
// Delete the temporary connection from Nango to prevent duplicates
|
|
321
|
+
try {
|
|
322
|
+
await nangoService.deleteConnection(connectionId, NANGO_INTEGRATIONS.GITHUB_USER);
|
|
323
|
+
console.log(`[nango-webhook] Deleted temp connection for returning user`);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
console.error(`[nango-webhook] Failed to delete temp connection:`, error);
|
|
327
|
+
// Non-fatal - continue anyway
|
|
328
|
+
}
|
|
329
|
+
// Check for auto-add using permanent connection
|
|
330
|
+
await checkAndAutoAddToWorkspaces(existingUser.id, existingUser.nangoConnectionId);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// SCENARIO 3: Existing user, first connection (or same connection)
|
|
334
|
+
console.log(`[nango-webhook] First/same connection for existing user: ${githubUser.login}`);
|
|
335
|
+
await db.users.update(existingUser.id, {
|
|
336
|
+
nangoConnectionId: connectionId,
|
|
337
|
+
incomingConnectionId: connectionId,
|
|
338
|
+
githubUsername: githubUser.login,
|
|
339
|
+
avatarUrl: githubUser.avatar_url || null,
|
|
340
|
+
});
|
|
341
|
+
// Update connection with user ID
|
|
342
|
+
await nangoService.updateEndUser(connectionId, NANGO_INTEGRATIONS.GITHUB_USER, {
|
|
343
|
+
id: existingUser.id,
|
|
344
|
+
email: existingUser.email || undefined,
|
|
345
|
+
});
|
|
346
|
+
// Check for auto-add to workspaces
|
|
347
|
+
await checkAndAutoAddToWorkspaces(existingUser.id, connectionId);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Handle Nango forward webhook (GitHub events forwarded by Nango)
|
|
351
|
+
*/
|
|
352
|
+
async function handleForwardWebhook(payload) {
|
|
353
|
+
const githubPayload = payload.payload;
|
|
354
|
+
console.log(`[nango-webhook] Forward event: action=${githubPayload.action} from ${payload.providerConfigKey}`);
|
|
355
|
+
// Only process GitHub App events
|
|
356
|
+
if (payload.providerConfigKey !== NANGO_INTEGRATIONS.GITHUB_APP) {
|
|
357
|
+
console.log('[nango-webhook] Ignoring forward event from non-GitHub-App integration');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
// Determine event type from payload structure
|
|
362
|
+
if (githubPayload.installation && githubPayload.action === 'created' && githubPayload.repositories) {
|
|
363
|
+
// Installation created event
|
|
364
|
+
await handleInstallationForward(githubPayload, payload.connectionId);
|
|
365
|
+
}
|
|
366
|
+
else if (githubPayload.repositories_added || githubPayload.repositories_removed) {
|
|
367
|
+
// Installation repositories added/removed
|
|
368
|
+
await handleInstallationRepositoriesForward(githubPayload, payload.connectionId);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
console.log(`[nango-webhook] Unhandled forward event structure: action=${githubPayload.action}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
console.error(`[nango-webhook] Error processing forward event:`, error);
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Handle GitHub installation events forwarded by Nango
|
|
381
|
+
*/
|
|
382
|
+
async function handleInstallationForward(body, connectionId) {
|
|
383
|
+
const { action, installation, repositories, sender } = body;
|
|
384
|
+
if (!installation || !sender)
|
|
385
|
+
return;
|
|
386
|
+
const installationId = String(installation.id);
|
|
387
|
+
console.log(`[nango-webhook] Installation ${action}: ${installation.account.login} (${installationId})`);
|
|
388
|
+
if (action === 'created') {
|
|
389
|
+
// Find user by GitHub ID
|
|
390
|
+
const user = await db.users.findByGithubId(String(sender.id));
|
|
391
|
+
// Create/update installation record
|
|
392
|
+
await db.githubInstallations.upsert({
|
|
393
|
+
installationId,
|
|
394
|
+
accountType: installation.account.type.toLowerCase(),
|
|
395
|
+
accountLogin: installation.account.login,
|
|
396
|
+
accountId: String(installation.account.id),
|
|
397
|
+
installedById: user?.id ?? null,
|
|
398
|
+
permissions: installation.permissions,
|
|
399
|
+
events: installation.events,
|
|
400
|
+
});
|
|
401
|
+
// Sync repositories if provided
|
|
402
|
+
if (repositories && user) {
|
|
403
|
+
const dbInstallation = await db.githubInstallations.findByInstallationId(installationId);
|
|
404
|
+
if (dbInstallation) {
|
|
405
|
+
const workspacesToJoin = new Set();
|
|
406
|
+
for (const repo of repositories) {
|
|
407
|
+
const syncedRepo = await db.repositories.upsert({
|
|
408
|
+
userId: user.id,
|
|
409
|
+
githubFullName: repo.full_name,
|
|
410
|
+
githubId: repo.id,
|
|
411
|
+
isPrivate: repo.private,
|
|
412
|
+
installationId: dbInstallation.id,
|
|
413
|
+
nangoConnectionId: connectionId,
|
|
414
|
+
syncStatus: 'synced',
|
|
415
|
+
lastSyncedAt: new Date(),
|
|
416
|
+
});
|
|
417
|
+
// Check if repo is part of an existing workspace
|
|
418
|
+
// Look for ANY user's record of this repo that has a workspaceId
|
|
419
|
+
if (syncedRepo.workspaceId) {
|
|
420
|
+
workspacesToJoin.add(syncedRepo.workspaceId);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
// Check if other users have this repo linked to a workspace
|
|
424
|
+
const allRepoRecords = await db.repositories.findByGithubFullName(repo.full_name);
|
|
425
|
+
for (const otherRecord of allRepoRecords) {
|
|
426
|
+
if (otherRecord.workspaceId && otherRecord.userId !== user.id) {
|
|
427
|
+
workspacesToJoin.add(otherRecord.workspaceId);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Auto-join user to workspaces for repos they have access to
|
|
433
|
+
for (const workspaceId of workspacesToJoin) {
|
|
434
|
+
const existingMembership = await db.workspaceMembers.findMembership(workspaceId, user.id);
|
|
435
|
+
if (!existingMembership) {
|
|
436
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
437
|
+
if (workspace) {
|
|
438
|
+
console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
|
|
439
|
+
await db.workspaceMembers.addMember({
|
|
440
|
+
workspaceId,
|
|
441
|
+
userId: user.id,
|
|
442
|
+
role: 'member',
|
|
443
|
+
invitedBy: workspace.userId,
|
|
444
|
+
});
|
|
445
|
+
await db.workspaceMembers.acceptInvite(workspaceId, user.id);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
console.log(`[nango-webhook] Installation created for ${installation.account.login}, auto-joined ${workspacesToJoin.size} workspaces`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Handle installation_repositories events forwarded by Nango
|
|
456
|
+
*/
|
|
457
|
+
async function handleInstallationRepositoriesForward(body, connectionId) {
|
|
458
|
+
const { action, installation, repositories_added, repositories_removed, sender } = body;
|
|
459
|
+
console.log(`[nango-webhook] handleInstallationRepositoriesForward called`, {
|
|
460
|
+
action,
|
|
461
|
+
installation: installation?.id,
|
|
462
|
+
sender: sender?.login,
|
|
463
|
+
connectionId,
|
|
464
|
+
repositories_added: repositories_added?.map(r => r.full_name),
|
|
465
|
+
repositories_removed: repositories_removed?.map(r => r.full_name),
|
|
466
|
+
});
|
|
467
|
+
if (!installation || !sender) {
|
|
468
|
+
console.log(`[nango-webhook] Missing installation or sender, skipping`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const installationId = String(installation.id);
|
|
472
|
+
console.log(`[nango-webhook] Repositories ${action} for ${installation.account.login}`);
|
|
473
|
+
// Find installation in database
|
|
474
|
+
const dbInstallation = await db.githubInstallations.findByInstallationId(installationId);
|
|
475
|
+
if (!dbInstallation) {
|
|
476
|
+
console.error(`[nango-webhook] Installation ${installationId} not found in database`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
console.log(`[nango-webhook] Found installation: ${dbInstallation.id}`);
|
|
480
|
+
// Find user who triggered this
|
|
481
|
+
const user = await db.users.findByGithubId(String(sender.id));
|
|
482
|
+
if (!user) {
|
|
483
|
+
console.error(`[nango-webhook] User ${sender.login} (github id: ${sender.id}) not found in database`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
console.log(`[nango-webhook] Found user: ${user.id} (${user.githubUsername})`);
|
|
487
|
+
console.log(`[nango-webhook] Processing action: ${action}, repos_added: ${repositories_added?.length}, repos_removed: ${repositories_removed?.length}`);
|
|
488
|
+
if (action === 'added' && repositories_added) {
|
|
489
|
+
console.log(`[nango-webhook] Adding ${repositories_added.length} repos for user ${user.id}`);
|
|
490
|
+
const workspacesToJoin = new Set();
|
|
491
|
+
for (const repo of repositories_added) {
|
|
492
|
+
console.log(`[nango-webhook] Upserting repo: ${repo.full_name}`);
|
|
493
|
+
const syncedRepo = await db.repositories.upsert({
|
|
494
|
+
userId: user.id,
|
|
495
|
+
githubFullName: repo.full_name,
|
|
496
|
+
githubId: repo.id,
|
|
497
|
+
isPrivate: repo.private,
|
|
498
|
+
installationId: dbInstallation.id,
|
|
499
|
+
nangoConnectionId: connectionId,
|
|
500
|
+
syncStatus: 'synced',
|
|
501
|
+
lastSyncedAt: new Date(),
|
|
502
|
+
});
|
|
503
|
+
console.log(`[nango-webhook] Upserted repo: ${syncedRepo.id}, syncStatus: ${syncedRepo.syncStatus}, nangoConnectionId: ${syncedRepo.nangoConnectionId}`);
|
|
504
|
+
// Check if repo is part of an existing workspace
|
|
505
|
+
// Look for ANY user's record of this repo that has a workspaceId
|
|
506
|
+
if (syncedRepo.workspaceId) {
|
|
507
|
+
workspacesToJoin.add(syncedRepo.workspaceId);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Check if other users have this repo linked to a workspace
|
|
511
|
+
const allRepoRecords = await db.repositories.findByGithubFullName(repo.full_name);
|
|
512
|
+
for (const otherRecord of allRepoRecords) {
|
|
513
|
+
if (otherRecord.workspaceId && otherRecord.userId !== user.id) {
|
|
514
|
+
workspacesToJoin.add(otherRecord.workspaceId);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Auto-join user to workspaces for repos they have access to
|
|
520
|
+
for (const workspaceId of workspacesToJoin) {
|
|
521
|
+
const existingMembership = await db.workspaceMembers.findMembership(workspaceId, user.id);
|
|
522
|
+
if (!existingMembership) {
|
|
523
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
524
|
+
if (workspace) {
|
|
525
|
+
console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
|
|
526
|
+
await db.workspaceMembers.addMember({
|
|
527
|
+
workspaceId,
|
|
528
|
+
userId: user.id,
|
|
529
|
+
role: 'member',
|
|
530
|
+
invitedBy: workspace.userId,
|
|
531
|
+
});
|
|
532
|
+
await db.workspaceMembers.acceptInvite(workspaceId, user.id);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
console.log(`[nango-webhook] Added ${repositories_added.length} repositories, auto-joined ${workspacesToJoin.size} workspaces`);
|
|
537
|
+
}
|
|
538
|
+
if (action === 'removed' && repositories_removed) {
|
|
539
|
+
for (const repo of repositories_removed) {
|
|
540
|
+
const repos = await db.repositories.findByUserId(user.id);
|
|
541
|
+
const existingRepo = repos.find(r => r.githubFullName === repo.full_name);
|
|
542
|
+
if (existingRepo) {
|
|
543
|
+
await db.repositories.updateSyncStatus(existingRepo.id, 'access_removed');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
console.log(`[nango-webhook] Removed access to ${repositories_removed.length} repositories`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Handle GitHub App OAuth webhook (repo access)
|
|
551
|
+
*/
|
|
552
|
+
async function handleRepoAuthWebhook(connectionId, endUser) {
|
|
553
|
+
let userId = endUser?.id;
|
|
554
|
+
// Fallback: If endUser.id not in webhook, fetch connection metadata from Nango
|
|
555
|
+
if (!userId) {
|
|
556
|
+
console.log('[nango-webhook] No user ID in webhook payload, fetching from connection metadata...');
|
|
557
|
+
try {
|
|
558
|
+
const connection = await nangoService.getConnection(connectionId, NANGO_INTEGRATIONS.GITHUB_APP);
|
|
559
|
+
userId = connection.end_user?.id;
|
|
560
|
+
console.log(`[nango-webhook] Got user ID from connection: ${userId || 'not found'}`);
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
console.error('[nango-webhook] Failed to fetch connection metadata:', err);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!userId) {
|
|
567
|
+
console.error('[nango-webhook] No user ID found - cannot sync repos');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const user = await db.users.findById(userId);
|
|
571
|
+
if (!user) {
|
|
572
|
+
console.error(`[nango-webhook] User ${userId} not found`);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
// Get the GitHub App installation ID
|
|
577
|
+
const githubInstallationId = await nangoService.getGithubAppInstallationId(connectionId);
|
|
578
|
+
let installationUuid = null;
|
|
579
|
+
if (githubInstallationId) {
|
|
580
|
+
// Find or create the github_installations record
|
|
581
|
+
let installation = await db.githubInstallations.findByInstallationId(String(githubInstallationId));
|
|
582
|
+
if (!installation) {
|
|
583
|
+
// Create a new installation record
|
|
584
|
+
// We need to get more info about the installation - for now use user info
|
|
585
|
+
installation = await db.githubInstallations.upsert({
|
|
586
|
+
installationId: String(githubInstallationId),
|
|
587
|
+
accountType: 'user', // Could be 'organization' - we'd need to detect this
|
|
588
|
+
accountLogin: user.githubUsername || 'unknown',
|
|
589
|
+
accountId: user.githubId || 'unknown',
|
|
590
|
+
installedById: user.id,
|
|
591
|
+
permissions: {},
|
|
592
|
+
events: [],
|
|
593
|
+
});
|
|
594
|
+
console.log(`[nango-webhook] Created installation record for ${githubInstallationId}`);
|
|
595
|
+
}
|
|
596
|
+
installationUuid = installation.id;
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
console.warn('[nango-webhook] Could not get installation ID from Nango connection');
|
|
600
|
+
}
|
|
601
|
+
// Fetch repos the user has access to
|
|
602
|
+
const { repositories: repos } = await nangoService.listGithubAppRepos(connectionId);
|
|
603
|
+
// Track workspaces to auto-join
|
|
604
|
+
const workspacesToJoin = new Set();
|
|
605
|
+
// Sync repos to database
|
|
606
|
+
for (const repo of repos) {
|
|
607
|
+
const syncedRepo = await db.repositories.upsert({
|
|
608
|
+
userId: user.id,
|
|
609
|
+
githubFullName: repo.full_name,
|
|
610
|
+
githubId: repo.id,
|
|
611
|
+
isPrivate: repo.private,
|
|
612
|
+
defaultBranch: repo.default_branch,
|
|
613
|
+
nangoConnectionId: connectionId,
|
|
614
|
+
installationId: installationUuid,
|
|
615
|
+
syncStatus: 'synced',
|
|
616
|
+
lastSyncedAt: new Date(),
|
|
617
|
+
});
|
|
618
|
+
// Check if this repo is part of an existing workspace
|
|
619
|
+
// Look for ANY user's record of this repo that has a workspaceId
|
|
620
|
+
if (syncedRepo.workspaceId) {
|
|
621
|
+
workspacesToJoin.add(syncedRepo.workspaceId);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
// Check if other users have this repo linked to a workspace
|
|
625
|
+
const allRepoRecords = await db.repositories.findByGithubFullName(repo.full_name);
|
|
626
|
+
for (const otherRecord of allRepoRecords) {
|
|
627
|
+
if (otherRecord.workspaceId && otherRecord.userId !== user.id) {
|
|
628
|
+
workspacesToJoin.add(otherRecord.workspaceId);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Auto-join user to workspaces for repos they have access to
|
|
634
|
+
for (const workspaceId of workspacesToJoin) {
|
|
635
|
+
// Check if already a member
|
|
636
|
+
const existingMembership = await db.workspaceMembers.findMembership(workspaceId, user.id);
|
|
637
|
+
if (!existingMembership) {
|
|
638
|
+
// Get workspace owner to use as invitedBy
|
|
639
|
+
const workspace = await db.workspaces.findById(workspaceId);
|
|
640
|
+
if (workspace) {
|
|
641
|
+
console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
|
|
642
|
+
await db.workspaceMembers.addMember({
|
|
643
|
+
workspaceId,
|
|
644
|
+
userId: user.id,
|
|
645
|
+
role: 'member',
|
|
646
|
+
invitedBy: workspace.userId, // Workspace owner invited them
|
|
647
|
+
});
|
|
648
|
+
// Auto-accept since they have GitHub repo access
|
|
649
|
+
await db.workspaceMembers.acceptInvite(workspaceId, user.id);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// Clear any pending installation request
|
|
654
|
+
await db.users.clearPendingInstallationRequest(user.id);
|
|
655
|
+
console.log(`[nango-webhook] Synced ${repos.length} repos for ${user.githubUsername} (installation: ${githubInstallationId || 'unknown'}), auto-joined ${workspacesToJoin.size} workspaces`);
|
|
656
|
+
// Note: We intentionally do NOT auto-provision workspaces here.
|
|
657
|
+
// Users should go through the onboarding flow at /app to:
|
|
658
|
+
// 1. Name their workspace
|
|
659
|
+
// 2. Choose which repos to include
|
|
660
|
+
// 3. Understand what they're creating
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
const err = error;
|
|
664
|
+
if (err.message?.includes('403')) {
|
|
665
|
+
// Org approval pending
|
|
666
|
+
await db.users.setPendingInstallationRequest(user.id);
|
|
667
|
+
console.log(`[nango-webhook] Org approval pending for ${user.githubUsername}`);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
//# sourceMappingURL=nango-auth.js.map
|