PRININIT / lib /src /mount.js
Rachit-Tw's picture
Upload 184 files
2668b98 verified
Raw
History Blame Contribute Delete
4.46 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInstallationOctokit = createInstallationOctokit;
exports.handleMount = handleMount;
const auth_app_1 = require("@octokit/auth-app");
const rest_1 = require("@octokit/rest");
const supabase_1 = require("./supabase");
/**
* Generates a GitHub App Installation Access Token and returns an
* authenticated Octokit instance scoped to that installation.
*/
async function createInstallationOctokit(installationId) {
const appId = process.env.APP_ID;
let privateKey = process.env.PRIVATE_KEY;
if (!appId || !privateKey) {
throw new Error('APP_ID and PRIVATE_KEY must be set in environment variables.');
}
// Sanitize private key (same as index.ts)
privateKey = privateKey.replace(/\\n/g, '\n').replace(/^"(.*)"$/, '$1');
const auth = (0, auth_app_1.createAppAuth)({
appId: Number(appId),
privateKey
});
// Request an installation access token
const installationAuth = await auth({
type: 'installation',
installationId
});
const octokit = new rest_1.Octokit({
auth: installationAuth.token
});
return {
octokit,
token: installationAuth.token,
expiresAt: installationAuth.expiresAt ||
new Date(Date.now() + 3600000).toISOString()
};
}
/**
* Lists all repositories accessible to the given installation.
* Handles pagination automatically.
*/
async function listInstallationRepos(octokit) {
const repos = [];
let page = 1;
const perPage = 100;
while (true) {
const response = await octokit.request('GET /installation/repositories', {
per_page: perPage,
page
});
const fetchedRepos = response.data.repositories || [];
for (const repo of fetchedRepos) {
repos.push(repo.full_name);
}
// If we got fewer than perPage, we've reached the last page
if (fetchedRepos.length < perPage) {
break;
}
page++;
}
return repos;
}
/**
* Updates the user record in Supabase with the installation ID and
* overwrites selected_repos with the repos from the installation.
*/
async function syncUserInstallation(githubId, installationId, repos) {
// UPSERT: Update if exists, otherwise this is an error
// (user should already exist from the OAuth signup flow)
const result = await supabase_1.db.query(`UPDATE users
SET github_installation_id = $1,
selected_repos = $2,
updated_at = NOW()
WHERE github_id = $3`, [installationId, repos, githubId]);
if (result.rowCount === 0) {
throw new Error(`No user found with github_id=${githubId}. User must sign up before mounting repositories.`);
}
}
/**
* Main mount handler — called by the Express route.
*/
async function handleMount(body) {
const { installationId, githubId } = body;
// 1. Validate
if (!installationId || !githubId) {
return {
success: false,
repos: [],
error: 'Missing required fields: installationId and githubId'
};
}
if (typeof installationId !== 'number' || typeof githubId !== 'number') {
return {
success: false,
repos: [],
error: 'installationId and githubId must be numbers'
};
}
try {
// 2. Generate Installation Access Token
console.log(`[MOUNT] Generating installation token for installation=${installationId}, user=${githubId}`);
const { octokit, expiresAt } = await createInstallationOctokit(installationId);
// 3. List accessible repos
const repos = await listInstallationRepos(octokit);
console.log(`[MOUNT] Found ${repos.length} repos for installation ${installationId}: ${repos.join(', ')}`);
// 4. Sync to database (overwrite strategy)
await syncUserInstallation(githubId, installationId, repos);
console.log(`[MOUNT] Successfully synced ${repos.length} repos for user ${githubId}`);
return {
success: true,
repos,
tokenExpiresAt: expiresAt
};
}
catch (err) {
console.error(`[MOUNT] Error: ${err.message}`);
return {
success: false,
repos: [],
error: err.message || 'Internal server error'
};
}
}
//# sourceMappingURL=mount.js.map