Skip to content

Commit

Permalink
Od/cli/preview command (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
aumkar authored Sep 25, 2023
1 parent 3b853da commit ccda645
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 9 deletions.
116 changes: 116 additions & 0 deletions packages/cli/src/cli/commands/documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { exit } from "process";
import chalk from "chalk";
import AddOnApiHelper from "../../lib/addonApiHelper";
import config from "../../lib/config";
import { Logger, SpinnerLogger } from "../../lib/logger";
import { filterUndefinedProperties, parameterize } from "../../lib/utils";
import { errorHandler } from "../exceptions";

type GeneratePreviewParam = {
documentId: string;
baseUrl: string;
};
function generateBaseAPIPath(siteData: Site, baseUrl?: string) {
if (!siteData) return "#";

const isPlayground = siteData.__isPlayground;

let _baseUrl: string;
if (baseUrl) _baseUrl = baseUrl;
else if (isPlayground) _baseUrl = config.playgroundUrl;
else _baseUrl = siteData.url;

return isPlayground
? `${_baseUrl}/api/${siteData.id}/pantheoncloud`
: `${_baseUrl}/api/pantheoncloud`;
}

async function generateDocumentPath(
site: Site,
docId: string,
isPreview: boolean,
{
baseUrl,
queryParams,
}: {
baseUrl?: string;
queryParams?: Record<string, string>;
},
) {
const augmentedQueryParams = { ...queryParams };

if (isPreview) {
augmentedQueryParams.pccGrant = await AddOnApiHelper.getPreviewJwt(site.id);
}

const params =
augmentedQueryParams == null
? {}
: filterUndefinedProperties(augmentedQueryParams);

return `${generateBaseAPIPath(site, baseUrl)}/document/${docId}${
Object.values(params).length > 0 ? `/?${parameterize(params)}` : ""
}`;
}

export const generatePreviewLink = errorHandler<GeneratePreviewParam>(
async ({ documentId, baseUrl }: GeneratePreviewParam) => {
let document: Article;
const logger = new Logger();

if (baseUrl) {
try {
new URL(baseUrl);
} catch (_err) {
logger.error(
chalk.red(
`ERROR: Value provided for \`baseUrl\` is not a valid URL. `,
),
);
exit(1);
}
}

// Fetching document details
const fetchLogger = new SpinnerLogger("Fetching document details...");
fetchLogger.start();
try {
document = await AddOnApiHelper.getDocument(documentId);
} catch (err) {
fetchLogger.stop();
if ((err as { response: { status: number } }).response.status === 404) {
logger.error(
chalk.red("ERROR: Article not found for given document ID."),
);
exit(1);
} else throw err;
}
let site: Site;
try {
site = await AddOnApiHelper.getSite(document.siteId);
} catch (err) {
fetchLogger.stop();
if ((err as { response: { status: number } }).response.status === 404) {
logger.error(chalk.red("ERROR: Site not found for given document."));
exit(1);
} else throw err;
}
fetchLogger.succeed("Fetched document details!");

// Generating link
const generateLinkLogger = new SpinnerLogger("Generating preview link");
generateLinkLogger.start();

const buildLink = `${await generateDocumentPath(site, documentId, true, {
queryParams: {
publishingLevel: "REALTIME",
},
baseUrl,
})}`;
generateLinkLogger.succeed(
"Successfully generated preview link. Please copy it from below:",
);

logger.log(chalk.green(buildLink));
},
);
52 changes: 43 additions & 9 deletions packages/cli/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { generatePreviewLink } from "./commands/documents";
import init from "./commands/init";
import login from "./commands/login";
import logout from "./commands/logout";
Expand Down Expand Up @@ -141,7 +142,7 @@ yargs(hideBin(process.argv))
"Revokes token for a given id.",
(yargs) => {
yargs.positional("<id>", {
describe: "ID of the token which you want to revoke",
describe: "ID of the token which you want to revoke.",
demandOption: true,
type: "string",
});
Expand Down Expand Up @@ -177,13 +178,13 @@ yargs(hideBin(process.argv))
"Shows component schema of the site.",
(yargs) => {
yargs.option("url", {
describe: "Site url",
describe: "Site url.",
type: "string",
demandOption: true,
});

yargs.option("apiPath", {
describe: "API path such as /api/pantheoncloud/component_schema",
describe: "API path such as /api/pantheoncloud/component_schema.",
type: "string",
demandOption: false,
});
Expand All @@ -204,12 +205,12 @@ yargs(hideBin(process.argv))
)
.command(
"configure <id> [options]",
"Configure properties for a given site",
"Configure properties for a given site.",
(yargs) => {
yargs
.strictCommands()
.positional("<id>", {
describe: "ID of the site which you want to configure",
describe: "ID of the site which you want to configure.",
demandOption: true,
type: "string",
})
Expand Down Expand Up @@ -245,24 +246,25 @@ yargs(hideBin(process.argv))
)
.command(
"webhooks <cmd> [options]",
"Manage webhooks for a given site",
"Manage webhooks for a given site.",
(yargs) => {
yargs
.strictCommands()
.demandCommand()
.command(
"history <id>",
"View webhook event delivery logs for a given site",
"View webhook event delivery logs for a given site.",
(yargs) => {
yargs
.strictCommands()
.positional("<id>", {
describe: "ID of the site for which you want to see logs",
describe:
"ID of the site for which you want to see logs.",
demandOption: true,
type: "string",
})
.option("limit", {
describe: "Number of logs to fetch at a time",
describe: "Number of logs to fetch at a time.",
type: "number",
default: 100,
demandOption: false,
Expand All @@ -281,6 +283,38 @@ yargs(hideBin(process.argv))
// noop
},
)
.command(
"document <cmd> [options]",
"Enables you to manage documents for a PCC Project.",
(yargs) => {
yargs
.strictCommands()
.demandCommand()
.command(
"preview <id>",
"Generates preview link for a given document ID.",
(yargs) => {
yargs
.strictCommands()
.positional("<id>", {
describe: "ID of the document.",
demandOption: true,
type: "string",
})
.option("baseUrl", {
describe: "Base URL for the generated preview link.",
type: "string",
demandOption: false,
});
},
async (args) =>
await generatePreviewLink({
documentId: args.id as string,
baseUrl: args.baseUrl as string,
}),
);
},
)
.command(
"login",
"Logs you in you to PCC client.",
Expand Down
39 changes: 39 additions & 0 deletions packages/cli/src/lib/addonApiHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getLocalAuthDetails } from "./localStorage";

const API_KEY_ENDPOINT = `${config.addOnApiEndpoint}/api-key`;
const SITE_ENDPOINT = `${config.addOnApiEndpoint}/sites`;
const DOCUMENT_ENDPOINT = `${config.addOnApiEndpoint}/articles`;
const OAUTH_ENDPOINT = `${config.addOnApiEndpoint}/oauth`;

class AddOnApiHelper {
Expand All @@ -22,6 +23,32 @@ class AddOnApiHelper {
return resp.data as Credentials;
}

static async getDocument(documentId: string): Promise<Article> {
const authDetails = await getLocalAuthDetails();
if (!authDetails) throw new UserNotLoggedIn();

const resp = await axios.get(`${DOCUMENT_ENDPOINT}/${documentId}`, {
headers: {
Authorization: `Bearer ${authDetails.id_token}`,
},
});

return resp.data as Article;
}

static async getPreviewJwt(siteId: string): Promise<string> {
const authDetails = await getLocalAuthDetails();
if (!authDetails) throw new UserNotLoggedIn();

const resp = await axios.post(`${SITE_ENDPOINT}/${siteId}/preview`, null, {
headers: {
Authorization: `Bearer ${authDetails.id_token}`,
},
});

return resp.data.grantToken as string;
}

static async createApiKey(): Promise<string> {
const authDetails = await getLocalAuthDetails();
if (!authDetails) throw new UserNotLoggedIn();
Expand Down Expand Up @@ -98,6 +125,18 @@ class AddOnApiHelper {

return resp.data as Site[];
}
static async getSite(siteId: string): Promise<Site> {
const authDetails = await getLocalAuthDetails();
if (!authDetails) throw new UserNotLoggedIn();

const resp = await axios.get(`${SITE_ENDPOINT}/${siteId}`, {
headers: {
Authorization: `Bearer ${authDetails.id_token}`,
},
});

return resp.data as Site;
}

static async updateSite(id: string, url: string): Promise<void> {
const authDetails = await getLocalAuthDetails();
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type Config = {
addOnApiEndpoint: string;
googleClientId: string;
googleRedirectUri: string;
playgroundUrl: string;
};
const ENV: Env = (process.env.NODE_ENV as Env) || "production";

Expand All @@ -17,18 +18,21 @@ const config: { [key in Env]: Config } = {
googleClientId:
"432998952749-6eurouamlt7mvacb6u4e913m3kg4774c.apps.googleusercontent.com",
googleRedirectUri: "http://localhost:3030/oauth-redirect",
playgroundUrl: "https://live-collabcms-fe-demo.appa.pantheon.site",
},
[Env.staging]: {
addOnApiEndpoint:
"https://us-central1-pantheon-content-cloud-staging.cloudfunctions.net/addOnApi",
googleClientId:
"142470191541-8o14j77pvagisc66s48kl4ub91f9c7b8.apps.googleusercontent.com",
googleRedirectUri: "http://localhost:3030/oauth-redirect",
playgroundUrl: "https://multi-staging-collabcms-fe-demo.appa.pantheon.site",
},
[Env.test]: {
addOnApiEndpoint: "https://test-jest.comxyz/addOnApi",
googleClientId: "test-google-com",
googleRedirectUri: "http://localhost:3030/oauth-redirect",
playgroundUrl: "https://test-playground.site",
},
};

Expand Down
13 changes: 13 additions & 0 deletions packages/cli/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function filterUndefinedProperties(obj: Record<string, any>) {
const _obj = obj;
Object.keys(_obj).forEach((key) =>
_obj[key] === undefined ? delete _obj[key] : {},
);
return _obj;
}
export function parameterize(obj: any, encode = true): string {
const func = encode ? encodeURIComponent : (s: string): string => s;
return Object.entries<string>(obj)
.map(([k, v]) => `${func(k)}=${func(v)}`)
.join("&");
}
7 changes: 7 additions & 0 deletions packages/cli/src/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
declare type CliTemplateOptions = "nextjs" | "gatsby";

declare type Article = {
id: string;
siteId: string;
title: string;
};

declare type ApiKey = {
id: string;
keyMasked: string;
Expand All @@ -10,6 +16,7 @@ declare type Site = {
id: string;
url: string;
created?: number;
__isPlayground: boolean;
};

declare type AuthDetails = {
Expand Down

0 comments on commit ccda645

Please sign in to comment.