Skip to content

Commit

Permalink
Merge pull request #35 from HubSpot/br-top-level-exports-1
Browse files Browse the repository at this point in the history
Porting hubdb top level export file
  • Loading branch information
brandenrodgers authored Sep 27, 2023
2 parents 534f7a8 + abed290 commit 15e7026
Show file tree
Hide file tree
Showing 7 changed files with 6,230 additions and 29 deletions.
33 changes: 25 additions & 8 deletions api/hubdb.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import http from '../http';
import { QueryParams } from '../types/Http';
import { CreateRowsResponse, Row, Schema, Table } from '../types/Hubdb';
import {
CreateRowsResponse,
FetchRowsResponse,
Row,
Schema,
Table,
} from '../types/Hubdb';

const HUBDB_API_PATH = 'cms/v3/hubdb';

export async function fetchTable(
accountId: number,
tableId: number
tableId: string
): Promise<Table> {
return http.get(accountId, {
url: `${HUBDB_API_PATH}/tables/${tableId}`,
Expand All @@ -23,9 +29,20 @@ export async function createTable(
});
}

export async function updateTable(
accountId: number,
tableId: string,
schema: Schema
) {
return http.patch(accountId, {
url: `${HUBDB_API_PATH}/tables/${tableId}/draft`,
body: schema,
});
}

export async function publishTable(
accountId: number,
tableId: number
tableId: string
): Promise<Table> {
return http.post(accountId, {
url: `${HUBDB_API_PATH}/tables/${tableId}/draft/publish`,
Expand All @@ -34,7 +51,7 @@ export async function publishTable(

export async function deleteTable(
accountId: number,
tableId: number
tableId: string
): Promise<void> {
return http.delete(accountId, {
url: `${HUBDB_API_PATH}/tables/${tableId}`,
Expand All @@ -43,7 +60,7 @@ export async function deleteTable(

export async function createRows(
accountId: number,
tableId: number,
tableId: string,
rows: Array<Row>
): Promise<CreateRowsResponse> {
return http.post(accountId, {
Expand All @@ -54,9 +71,9 @@ export async function createRows(

export async function fetchRows(
accountId: number,
tableId: number,
tableId: string,
query: QueryParams = {}
): Promise<Array<Row>> {
): Promise<FetchRowsResponse> {
return http.get(accountId, {
url: `${HUBDB_API_PATH}/tables/${tableId}/rows/draft`,
query,
Expand All @@ -65,7 +82,7 @@ export async function fetchRows(

export async function deleteRows(
accountId: number,
tableId: number,
tableId: string,
rowIds: Array<string>
): Promise<void> {
return http.post(accountId, {
Expand Down
3 changes: 3 additions & 0 deletions lang/en.lyaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ en:
logging:
creatingFile: "Creating file at {{ path }}"
errors:
hubdb:
invalidJsonPath: "The HubDB table file must be a '.json' file"
invalidJsonFile: "The '{{{ src }}' path is not a path to a file"
archive:
extractZip:
write: "An error occured writing temp project source."
Expand Down
204 changes: 204 additions & 0 deletions lib/hubdb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import path from 'path';
import fs from 'fs-extra';
import prettier from 'prettier';
import {
createTable,
updateTable,
createRows,
fetchTable,
fetchRows,
publishTable,
deleteRows,
} from '../api/hubdb';
import { getCwd } from './path';
import { FetchRowsResponse, Row, Table } from '../types/Hubdb';
import { throwErrorWithMessage } from '../errors/standardErrors';
import { BaseError } from '../types/Error';

function validateJsonPath(src: string): void {
if (path.extname(src) !== '.json') {
throwErrorWithMessage('hubdb.invalidJsonPath');
}
}

function validateJsonFile(src: string): void {
let stats;

try {
stats = fs.statSync(src);
} catch (err) {
throwErrorWithMessage('hubdb.invalidJsonFile', { src }, err as BaseError);
}

if (!stats.isFile()) {
throwErrorWithMessage('hubdb.invalidJsonFile', { src });
}

validateJsonPath(src);
}

export async function addRowsToHubDbTable(
accountId: number,
tableId: string,
rows: Array<Row>
): Promise<{ tableId: string; rowCount: number }> {
const rowsToUpdate = rows.map(row => {
const values = row.values;

return {
childTableId: '0',
isSoftEditable: false,
...row,
values,
};
});

if (rowsToUpdate.length > 0) {
await createRows(accountId, tableId, rowsToUpdate);
}

const { rowCount } = await publishTable(accountId, tableId);

return {
tableId,
rowCount,
};
}

export async function createHubDbTable(
accountId: number,
src: string
): Promise<{ tableId: string; rowCount: number }> {
validateJsonFile(src);

const table: Table = fs.readJsonSync(src);
const { rows, ...schema } = table;
const { id } = await createTable(accountId, schema);

return addRowsToHubDbTable(accountId, id, rows);
}

export async function updateHubDbTable(
accountId: number,
tableId: string,
src: string
) {
validateJsonFile(src);

const table: Table = fs.readJsonSync(src);
const { ...schema } = table;

return updateTable(accountId, tableId, schema);
}

function convertToJSON(table: Table, rows: Array<Row>) {
const {
allowChildTables,
allowPublicApiAccess,
columns,
dynamicMetaTags,
enableChildTablePages,
label,
name,
useForPages,
} = table;

const cleanedColumns = columns
.filter(column => !column.deleted || !column.archived)
.map(
({
/* eslint-disable @typescript-eslint/no-unused-vars */
id,
deleted,
archived,
foreignIdsByName,
foreignIdsById,
/* eslint-enable @typescript-eslint/no-unused-vars */
...cleanedColumn
}) => cleanedColumn
);

const cleanedRows = rows.map(row => {
return {
path: row.path,
name: row.name,
values: row.values,
};
});

return {
name,
useForPages,
label,
allowChildTables,
allowPublicApiAccess,
dynamicMetaTags,
enableChildTablePages,
columns: cleanedColumns,
rows: cleanedRows,
};
}

async function fetchAllRows(
accountId: number,
tableId: string
): Promise<Array<Row>> {
let rows: Array<Row> = [];
let after: string | null = null;
do {
const response: FetchRowsResponse = await fetchRows(
accountId,
tableId,
after ? { after } : undefined
);

const { paging, results } = response;

rows = rows.concat(results);
after = paging && paging.next ? paging.next.after : null;
} while (after !== null);

return rows;
}

export async function downloadHubDbTable(
accountId: number,
tableId: string,
dest: string
): Promise<{ filePath: string }> {
const table = await fetchTable(accountId, tableId);

dest = path.resolve(getCwd(), dest || `${table.name}.hubdb.json`) as string;

if (fs.pathExistsSync(dest)) {
validateJsonFile(dest);
} else {
validateJsonPath(dest);
}

const rows = await fetchAllRows(accountId, tableId);
const tableToWrite = JSON.stringify(convertToJSON(table, rows));
const tableJson = await prettier.format(tableToWrite, {
parser: 'json',
});

await fs.outputFile(dest, tableJson);

return { filePath: dest };
}

export async function clearHubDbTableRows(
accountId: number,
tableId: string
): Promise<{ deletedRowCount: number }> {
const rows = await fetchAllRows(accountId, tableId);
await deleteRows(
accountId,
tableId,
rows.map(row => row.id)
);

return {
deletedRowCount: rows.length,
};
}
Loading

0 comments on commit 15e7026

Please sign in to comment.