mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
346 lines
8.6 KiB
TypeScript
346 lines
8.6 KiB
TypeScript
import type { OptionsWithUri } from 'request';
|
|
|
|
import type {
|
|
IDataObject,
|
|
IExecuteFunctions,
|
|
ILoadOptionsFunctions,
|
|
IPollFunctions,
|
|
JsonObject,
|
|
} from 'n8n-workflow';
|
|
import { NodeApiError } from 'n8n-workflow';
|
|
|
|
import type { TDtableMetadataColumns, TDtableViewColumns, TEndpointVariableName } from './types';
|
|
|
|
import { schema } from './Schema';
|
|
|
|
import type {
|
|
ICredential,
|
|
ICtx,
|
|
IDtableMetadataColumn,
|
|
IEndpointVariables,
|
|
IName,
|
|
IRow,
|
|
IRowObject,
|
|
} from './Interfaces';
|
|
|
|
const userBaseUri = (uri?: string) => {
|
|
if (uri === undefined) {
|
|
return uri;
|
|
}
|
|
|
|
if (uri.endsWith('/')) {
|
|
return uri.slice(0, -1);
|
|
}
|
|
|
|
return uri;
|
|
};
|
|
|
|
export function resolveBaseUri(ctx: ICtx) {
|
|
return ctx?.credentials?.environment === 'cloudHosted'
|
|
? 'https://cloud.seatable.io'
|
|
: userBaseUri(ctx?.credentials?.domain);
|
|
}
|
|
|
|
export async function getBaseAccessToken(
|
|
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
|
ctx: ICtx,
|
|
) {
|
|
if (ctx?.base?.access_token !== undefined) {
|
|
return;
|
|
}
|
|
|
|
const options: OptionsWithUri = {
|
|
headers: {
|
|
Authorization: `Token ${ctx?.credentials?.token}`,
|
|
},
|
|
uri: `${resolveBaseUri(ctx)}/api/v2.1/dtable/app-access-token/`,
|
|
json: true,
|
|
};
|
|
|
|
ctx.base = await this.helpers.request(options);
|
|
}
|
|
|
|
function endpointCtxExpr(ctx: ICtx, endpoint: string): string {
|
|
const endpointVariables: IEndpointVariables = {};
|
|
endpointVariables.access_token = ctx?.base?.access_token;
|
|
endpointVariables.dtable_uuid = ctx?.base?.dtable_uuid;
|
|
|
|
return endpoint.replace(
|
|
/({{ *(access_token|dtable_uuid|server) *}})/g,
|
|
(match: string, expr: string, name: TEndpointVariableName) => {
|
|
return endpointVariables[name] || match;
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function seaTableApiRequest(
|
|
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
|
ctx: ICtx,
|
|
method: string,
|
|
endpoint: string,
|
|
|
|
body: any = {},
|
|
qs: IDataObject = {},
|
|
url: string | undefined = undefined,
|
|
option: IDataObject = {},
|
|
): Promise<any> {
|
|
const credentials = await this.getCredentials('seaTableApi');
|
|
|
|
ctx.credentials = credentials as unknown as ICredential;
|
|
|
|
await getBaseAccessToken.call(this, ctx);
|
|
|
|
const options: OptionsWithUri = {
|
|
headers: {
|
|
Authorization: `Token ${ctx?.base?.access_token}`,
|
|
},
|
|
method,
|
|
qs,
|
|
body,
|
|
uri: url || `${resolveBaseUri(ctx)}${endpointCtxExpr(ctx, endpoint)}`,
|
|
json: true,
|
|
};
|
|
|
|
if (Object.keys(body as IDataObject).length === 0) {
|
|
delete options.body;
|
|
}
|
|
|
|
if (Object.keys(option).length !== 0) {
|
|
Object.assign(options, option);
|
|
}
|
|
|
|
try {
|
|
return await this.helpers.request(options);
|
|
} catch (error) {
|
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
|
}
|
|
}
|
|
|
|
export async function setableApiRequestAllItems(
|
|
this: IExecuteFunctions | IPollFunctions,
|
|
ctx: ICtx,
|
|
propertyName: string,
|
|
method: string,
|
|
endpoint: string,
|
|
body: IDataObject,
|
|
query?: IDataObject,
|
|
): Promise<any> {
|
|
if (query === undefined) {
|
|
query = {};
|
|
}
|
|
const segment = schema.rowFetchSegmentLimit;
|
|
query.start = 0;
|
|
query.limit = segment;
|
|
|
|
const returnData: IDataObject[] = [];
|
|
|
|
let responseData;
|
|
|
|
do {
|
|
responseData = (await seaTableApiRequest.call(
|
|
this,
|
|
ctx,
|
|
method,
|
|
endpoint,
|
|
body,
|
|
query,
|
|
)) as unknown as IRow[];
|
|
//@ts-ignore
|
|
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
|
query.start = +query.start + segment;
|
|
} while (responseData && responseData.length > segment - 1);
|
|
|
|
return returnData;
|
|
}
|
|
|
|
export async function getTableColumns(
|
|
this: ILoadOptionsFunctions | IExecuteFunctions | IPollFunctions,
|
|
tableName: string,
|
|
ctx: ICtx = {},
|
|
): Promise<TDtableMetadataColumns> {
|
|
const {
|
|
metadata: { tables },
|
|
} = await seaTableApiRequest.call(
|
|
this,
|
|
ctx,
|
|
'GET',
|
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/metadata',
|
|
);
|
|
for (const table of tables) {
|
|
if (table.name === tableName) {
|
|
return table.columns;
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
export async function getTableViews(
|
|
this: ILoadOptionsFunctions | IExecuteFunctions,
|
|
tableName: string,
|
|
ctx: ICtx = {},
|
|
): Promise<TDtableViewColumns> {
|
|
const { views } = await seaTableApiRequest.call(
|
|
this,
|
|
ctx,
|
|
'GET',
|
|
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/views',
|
|
{},
|
|
{ table_name: tableName },
|
|
);
|
|
return views;
|
|
}
|
|
|
|
export function simplify(data: { results: IRow[] }, metadata: IDataObject) {
|
|
return data.results.map((row: IDataObject) => {
|
|
for (const key of Object.keys(row)) {
|
|
if (!key.startsWith('_')) {
|
|
row[metadata[key] as string] = row[key];
|
|
delete row[key];
|
|
}
|
|
}
|
|
return row;
|
|
});
|
|
}
|
|
|
|
export function getColumns(data: { metadata: [{ key: string; name: string }] }) {
|
|
return data.metadata.reduce(
|
|
(obj, value) => Object.assign(obj, { [`${value.key}`]: value.name }),
|
|
{},
|
|
);
|
|
}
|
|
|
|
export function getDownloadableColumns(data: {
|
|
metadata: [{ key: string; name: string; type: string }];
|
|
}) {
|
|
return data.metadata.filter((row) => ['image', 'file'].includes(row.type)).map((row) => row.name);
|
|
}
|
|
|
|
const uniquePredicate = (current: string, index: number, all: string[]) =>
|
|
all.indexOf(current) === index;
|
|
const nonInternalPredicate = (name: string) => !Object.keys(schema.internalNames).includes(name);
|
|
const namePredicate = (name: string) => (named: IName) => named.name === name;
|
|
export const nameOfPredicate = (names: readonly IName[]) => (name: string) =>
|
|
names.find(namePredicate(name));
|
|
|
|
const normalize = (subject: string): string => (subject ? subject.normalize() : '');
|
|
|
|
export const split = (subject: string): string[] =>
|
|
normalize(subject)
|
|
.split(/\s*((?:[^\\,]*?(?:\\[\s\S])*)*?)\s*(?:,|$)/)
|
|
.filter((s) => s.length)
|
|
.map((s) => s.replace(/\\([\s\S])/gm, ($0, $1) => $1));
|
|
|
|
export function columnNamesToArray(columnNames: string): string[] {
|
|
return columnNames ? split(columnNames).filter(nonInternalPredicate).filter(uniquePredicate) : [];
|
|
}
|
|
|
|
export function columnNamesGlob(
|
|
columnNames: string[],
|
|
dtableColumns: TDtableMetadataColumns,
|
|
): string[] {
|
|
const buffer: string[] = [];
|
|
const names: string[] = dtableColumns.map((c) => c.name).filter(nonInternalPredicate);
|
|
columnNames.forEach((columnName) => {
|
|
if (columnName !== '*') {
|
|
buffer.push(columnName);
|
|
return;
|
|
}
|
|
buffer.push(...names);
|
|
});
|
|
return buffer.filter(uniquePredicate);
|
|
}
|
|
|
|
/**
|
|
* sequence rows on _seq
|
|
*/
|
|
export function rowsSequence(rows: IRow[]) {
|
|
const l = rows.length;
|
|
if (l) {
|
|
const [first] = rows;
|
|
if (first?._seq !== undefined) {
|
|
return;
|
|
}
|
|
}
|
|
for (let i = 0; i < l; ) {
|
|
rows[i]._seq = ++i;
|
|
}
|
|
}
|
|
|
|
export function rowDeleteInternalColumns(row: IRow): IRow {
|
|
Object.keys(schema.internalNames).forEach((columnName) => delete row[columnName]);
|
|
return row;
|
|
}
|
|
|
|
function rowFormatColumn(input: unknown): boolean | number | string | string[] | null {
|
|
if (null === input || undefined === input) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof input === 'boolean' || typeof input === 'number' || typeof input === 'string') {
|
|
return input;
|
|
}
|
|
|
|
if (Array.isArray(input) && input.every((i) => typeof i === 'string')) {
|
|
return input;
|
|
} else if (Array.isArray(input) && input.every((i) => typeof i === 'object')) {
|
|
const returnItems = [] as string[];
|
|
input.every((i) => returnItems.push(i.display_value as string));
|
|
return returnItems;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function rowFormatColumns(row: IRow, columnNames: string[]): IRow {
|
|
const outRow = {} as IRow;
|
|
columnNames.forEach((c) => (outRow[c] = rowFormatColumn(row[c])));
|
|
return outRow;
|
|
}
|
|
|
|
export function rowsFormatColumns(rows: IRow[], columnNames: string[]) {
|
|
rows = rows.map((row) => rowFormatColumns(row, columnNames));
|
|
}
|
|
|
|
export function rowMapKeyToName(row: IRow, columns: TDtableMetadataColumns): IRow {
|
|
const mappedRow = {} as IRow;
|
|
|
|
// move internal columns first
|
|
Object.keys(schema.internalNames).forEach((key) => {
|
|
if (row[key]) {
|
|
mappedRow[key] = row[key];
|
|
delete row[key];
|
|
}
|
|
});
|
|
|
|
// pick each by its key for name
|
|
Object.keys(row).forEach((key) => {
|
|
const column = columns.find((c) => c.key === key);
|
|
if (column) {
|
|
mappedRow[column.name] = row[key];
|
|
}
|
|
});
|
|
|
|
return mappedRow;
|
|
}
|
|
|
|
export function rowExport(row: IRowObject, columns: TDtableMetadataColumns): IRowObject {
|
|
for (const columnName of Object.keys(columns)) {
|
|
if (!columns.find(namePredicate(columnName))) {
|
|
delete row[columnName];
|
|
}
|
|
}
|
|
return row;
|
|
}
|
|
|
|
export const dtableSchemaIsColumn = (column: IDtableMetadataColumn): boolean =>
|
|
!!schema.columnTypes[column.type];
|
|
|
|
const dtableSchemaIsUpdateAbleColumn = (column: IDtableMetadataColumn): boolean =>
|
|
!!schema.columnTypes[column.type] && !schema.nonUpdateAbleColumnTypes[column.type];
|
|
|
|
export const dtableSchemaColumns = (columns: TDtableMetadataColumns): TDtableMetadataColumns =>
|
|
columns.filter(dtableSchemaIsColumn);
|
|
|
|
export const updateAble = (columns: TDtableMetadataColumns): TDtableMetadataColumns =>
|
|
columns.filter(dtableSchemaIsUpdateAbleColumn);
|