diff --git a/cli/src/endpoints/authAxios.ts b/cli/src/endpoints/authAxios.ts new file mode 100644 index 0000000..74769c3 --- /dev/null +++ b/cli/src/endpoints/authAxios.ts @@ -0,0 +1,25 @@ +import axios from "axios"; + +export const axiosInstance = axios.create({ + withCredentials: true, // Ensure credentials are sent with every request +}); + +export const config = { + baseUrl: "https://routes-orcin.vercel.app", +}; + +export function SetCookie(credentials: string) { + // Add a request interceptor to include the cookie in the headers + axiosInstance.interceptors.request.use( + (config) => { + // Set the cookie in the header + config.headers.Cookie = credentials; + + return config; + }, + (error) => { + // Do something with request error + return Promise.reject(error); + }, + ); +} diff --git a/cli/src/endpoints/getProjects.ts b/cli/src/endpoints/getProjects.ts new file mode 100644 index 0000000..609b773 --- /dev/null +++ b/cli/src/endpoints/getProjects.ts @@ -0,0 +1,14 @@ +import type { Project } from "../types"; +import { axiosInstance, config } from "./authAxios"; + +export default async function GetProjects() { + const fullUrl = `${config.baseUrl}/api/trpc/database.get?input=${encodeURIComponent(JSON.stringify({ json: { searchTerms: "" } }))}`; + + const response = await axiosInstance.get(fullUrl); + + const { result } = response.data as { result: { data: { json: Project[] } } }; + + const projects = result.data.json; + + return projects; +} diff --git a/cli/src/index.ts b/cli/src/index.ts index 47df1b3..3e5403f 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -1,140 +1,54 @@ #!/usr/bin/env node -import inquirer from "inquirer"; -import parse, { type Config } from "parse-git-config"; -import type { CLIAnswers, EndpointResponse } from "./types"; -import parseGithubUrl from "parse-github-url"; -import axios from "axios"; -import updateExistingEnvVariable from "./utils/updateEnv"; import SetupCLI from "./menu/setup"; -import { exit } from "process"; - -export const baseUrl = "https://routes-orcin.vercel.app/"; -export const apiPath = "api/trpc/"; -const createDatabasePath = "database.create"; -const endpointPath = "database.endpoint"; +import ViewProjects from "./menu/projects"; +import inquirer from "inquirer"; -// Create an Axios instance -const axiosInstance = axios.create({ - withCredentials: true, // Ensure credentials are sent with every request -}); +type MainAnswers = { + selectedMenu: "setup" | "projects" | "exit"; +}; -async function askRetry(): Promise { - const answers = (await inquirer.prompt([ +async function GetMainAnswers(): Promise { + return (await inquirer.prompt([ { - type: "confirm", - name: "retry", - message: "Do you want to try updating the environment variable again?", - default: false, + type: "list", + name: "selectedMenu", + message: "Select a menu option", + choices: ["setup", "projects", "exit"], }, - ])) as { retry: boolean }; - return answers.retry; + ])) as MainAnswers; } -//this is where the cli code is generated to ensure we are able to get the user info +/** + * 1. Setup CLI (Auto setup if env vars are missing) + * 2. Databases + * a. Add Project + * b. Project 1 + * i. Pause/Start + * ii. Delete + * iii. main (active db) + * 1. Set connection url to env + * 2. Generate sample data + * iv. test-branch (no db) + * 1. Push database + * ... + * c. Project 3 + */ async function main() { - await SetupCLI(); - - process.exit(1); - /* - const config: Config = parse.expandKeys(parse.sync()); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const repoUrl = config?.remote?.origin?.url; - - const answers: CLIAnswers = (await inquirer.prompt([ - { - type: "list", - name: "backendLanguage", - message: - "What backend language are you using? (Other clients will be added later)", - choices: ["TypeScript"], - }, - { - type: "list", - name: "dbProvider", - message: "What database provider are you using?", - choices: ["mysql", "postgres"], - }, - { - type: "confirm", - name: "deployDatabase", - message: "Do you want to deploy a database to the cloud?", - }, - { - type: "confirm", - name: "correctRepo", - message: `Is ${repoUrl} your correct repo?`, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - when: (answers) => answers.deployDatabase === true, - }, - ])) as CLIAnswers; - - if (answers.deployDatabase) { - const parsedUrl = parseGithubUrl(repoUrl as string); - console.log("Name:", parsedUrl?.name); // repositoryName - - if (!repoUrl) { - console.error("No repo found in git config"); + const { selectedMenu } = await GetMainAnswers(); + + switch (selectedMenu) { + case "setup": + await SetupCLI(); + break; + case "projects": + await ViewProjects(); + break; + default: process.exit(1); - } - - console.log("Deploying database..."); - // Send request to backend to create database - try { - await axiosInstance.post(`${baseUrl}${apiPath}${createDatabasePath}`, { - json: { - repoUrl: repoUrl as string, - provider: answers.dbProvider, - }, - }); - } catch (error) { - console.warn("Database on this branch has already been deployed"); - } - - console.log("Deploying, this may take up to 10 minutes"); - - // Send request to backend to create database - for (let i = 0; i < 120; i++) { - try { - const endpointResponse = await axiosInstance.post( - `${baseUrl}${apiPath}${endpointPath}`, - { - json: { - repoUrl: repoUrl as string, - }, - }, - ); - - if (endpointResponse) { - const endpointData = endpointResponse.data as EndpointResponse; - console.log("Connection information:\n"); - console.log("\t" + endpointData.result.data.json.connection); - console.log(); + } - // Automatically set connection as environment URL - let retrySetEnv = true; - while (retrySetEnv) { - const success = await updateExistingEnvVariable( - "DATABASE_URL", - endpointData.result.data.json.connection, - ); - if (success) { - console.log("Environment variable updated successfully."); - break; - } else { - console.error("Failed to update environment variable."); - retrySetEnv = await askRetry(); - } - } - break; - } - } catch (error) { - await new Promise((resolve) => setTimeout(resolve, 10000)); - console.warn("Retrying..."); - } - } - } */ + await main(); } await main(); diff --git a/cli/src/menu/projects.ts b/cli/src/menu/projects.ts new file mode 100644 index 0000000..6abd494 --- /dev/null +++ b/cli/src/menu/projects.ts @@ -0,0 +1,87 @@ +import inquirer from "inquirer"; +import GetProjects from "../endpoints/getProjects"; +import type { Project } from "../types"; + +type ProjectAnswers = { + selectedProject: string; +}; + +const addProjectLabel = "Add a new project"; + +async function SelectProject(projectNames: string[]): Promise { + return (await inquirer.prompt([ + { + type: "list", + name: "selectedProject", + message: "Select a project", + choices: [addProjectLabel].concat(projectNames).concat(["back"]), + }, + ])) as ProjectAnswers; +} + +type ProjectOptionsAnswers = { + selectedOption: string; +}; + +async function SelectProjectOption( + validOptions: string[], +): Promise { + return (await inquirer.prompt([ + { + type: "list", + name: "selectedProject", + message: "Select a project", + choices: validOptions, + }, + ])) as ProjectOptionsAnswers; +} + +export default async function ViewProjects() { + const projects = await GetProjects(); + + const { selectedProject: selectedProjectName } = await SelectProject( + projects.map((proj) => proj.repository), + ); + + if (selectedProjectName === addProjectLabel) { + } else if (selectedProjectName === "back") { + // Do nothing + } else { + const selectedProject = + projects[ + projects.findIndex((proj) => proj.repository === selectedProjectName) + ]; + + await ViewProjectOptions(selectedProject); + } +} + +async function ViewProjectOptions(project: Project) { + console.log(project); + const { status } = project; + + const validOptions = + status === "available" + ? ["stop", "delete", "back"] + : ["start", "delete", "back"]; + + const { selectedOption } = await SelectProjectOption(validOptions); + + switch (selectedOption) { + case "start": + break; + case "stop": + break; + case "create": + break; + case "delete": + break; + case "endpoint": + break; + case "back": + // Do nothing + break; + } + + await ViewProjects(); +} diff --git a/cli/src/menu/setup.ts b/cli/src/menu/setup.ts index 0c378d1..22f650e 100644 --- a/cli/src/menu/setup.ts +++ b/cli/src/menu/setup.ts @@ -1,7 +1,7 @@ import inquirer from "inquirer"; -import axios from "axios"; import { GetCredentials } from "../utils/getCredentials"; import updateExistingEnvVariable from "../utils/updateEnv"; +import { axiosInstance, SetCookie, config } from "../endpoints/authAxios"; type SetupAnswers = { useGenerateBackend: boolean; @@ -36,57 +36,42 @@ export default async function SetupCLI() { const backendUrl = alternateBackendUrl ?? "https://routes-orcin.vercel.app"; - // Create an Axios instance - const axiosInstance = axios.create({ - withCredentials: true, // Ensure credentials are sent with every request - }); - // Verify you can connect to backend try { console.log(`Attempting to reach ${backendUrl}`); const { status } = await axiosInstance.get(`${backendUrl}/api/health`); if (status !== 200) { - console.error("DevDB backend url not healthy, exiting..."); + console.error("❌ DevDB backend url not healthy, exiting..."); process.exit(1); } else { await updateExistingEnvVariable("DEVDB_URL", backendUrl); - console.log("Successfully reached the backend"); + config.baseUrl = backendUrl; + console.log("✅ Successfully reached the backend"); } } catch (error) { - console.error("DevDB backend url not healthy, exiting..."); + console.error("❌ DevDB backend url not healthy, exiting..."); process.exit(1); - } finally { } // Verify session token is valid try { console.log(`Attempting to authenticated token`); - const credentials = await GetCredentials(backendUrl, sessionToken); - // Add a request interceptor to include the cookie in the headers - axiosInstance.interceptors.request.use( - (config) => { - // Set the cookie in the header - config.headers.Cookie = credentials; + const credentials = await GetCredentials(config.baseUrl, sessionToken); + SetCookie(credentials); - return config; - }, - (error) => { - // Do something with request error - return Promise.reject(error); - }, + const { status } = await axiosInstance.get( + `${config.baseUrl}/api/trpc/health`, ); - - const { status } = await axiosInstance.get(`${backendUrl}/api/trpc/health`); if (status !== 200) { - console.error("Token not authenticated, exiting..."); + console.error("❌ Token not authenticated, exiting..."); process.exit(1); } else { await updateExistingEnvVariable("DEVDB_TOKEN", sessionToken); - console.log("Successfully authenticated the token"); + console.log("✅ Successfully authenticated the token"); } } catch (error) { console.log(error); - console.error("Token not authenticated, exiting..."); + console.error("❌ Token not authenticated, exiting..."); process.exit(1); } } diff --git a/cli/src/types.ts b/cli/src/types.ts index 7ff35ad..17f07fc 100644 --- a/cli/src/types.ts +++ b/cli/src/types.ts @@ -15,3 +15,17 @@ export type EndpointResponse = { }; }; }; + +export type Project = { + owner: string; + ownerName: string; + repository: string; + repositoryName: string; + createdAt: Date; + updatedAt: Date; + createdById: string; + rdsInstanceId: string; + createdBy: { name: string }; + //branches: [[Object]]; + status: string; +}; diff --git a/src/server/api/tests/test-webhook.ts b/src/server/api/tests/test-webhook.ts deleted file mode 100644 index 5a73f7e..0000000 --- a/src/server/api/tests/test-webhook.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { App } from "octokit"; -import { readFileSync } from "fs"; - -async function testWebHook(): Promise { - const repo = "cy2550"; - - // Placeholder... need to figure out how to get access token - const owner = "wyattchris"; - - const privatePem = readFileSync("private-key.pem", { - encoding: "utf-8", - }); - - const app = new App({ - appId: process.env.GITHUB_APP_ID ?? "", - privateKey: privatePem, - }); - - const response = await app.octokit.request( - "GET /repos/{owner}/{repo}/installation", - { - owner: "wyattchris", - repo: "cy2550", - headers: { - "X-GitHub-Api-Version": "2022-11-28", - }, - }, - ); - - const installationId = response.data.id; - - const octokit = await app.getInstallationOctokit(installationId); - - try { - const response = await octokit.request( - `POST /repos/${owner}/${repo}/hooks`, - { - owner: "wyattchris", - repo: "cy2550", - name: "web", - active: true, - events: ["push", "pull_request"], - config: { - url: "https://example.com/webhook", - content_type: "json", - insecure_ssl: "1", - }, - headers: { - "X-GitHub-Api-Version": "2022-11-28", - }, - }, - ); - - // Assuming successful creation of webhook - console.log(response.data); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.data; - } catch (error) { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - throw new Error("Failed to create webhook: " + error); - } -} - -testWebHook() - .then(() => console.log("Webhook created!")) - .catch((error) => console.error(error));