n8n/packages/nodes-base/nodes/SeaTable/v2/GenericFunctions.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

362 lines
9.8 KiB
TypeScript
Raw Normal View History

2024-06-26 04:16:20 -07:00
import type FormData from 'form-data';
2023-10-25 17:20:43 -07:00
import type {
IDataObject,
IExecuteFunctions,
ILoadOptionsFunctions,
IPollFunctions,
JsonObject,
IHttpRequestMethods,
2024-06-25 04:32:45 -07:00
IRequestOptions,
2023-10-25 17:20:43 -07:00
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
2024-06-26 04:16:20 -07:00
import moment from 'moment';
2023-10-25 17:20:43 -07:00
import type { TDtableMetadataColumns, TEndpointVariableName } from './types';
import { schema } from './Schema';
import type {
ICollaborator,
ICollaboratorsResult,
ICredential,
ICtx,
IDtableMetadataColumn,
IEndpointVariables,
IName,
IRow,
IRowObject,
IColumnDigitalSignature,
IFile,
} from './actions/Interfaces';
// remove last backslash
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;
2024-06-25 04:32:45 -07:00
const options: IRequestOptions = {
2023-10-25 17:20:43 -07:00
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,
2024-06-25 20:21:25 -07:00
(match: string, name: TEndpointVariableName) => {
2023-10-25 17:20:43 -07:00
return (endpointVariables[name] as string) || match;
},
);
}
export async function seaTableApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
ctx: ICtx,
method: IHttpRequestMethods,
endpoint: string,
body: IDataObject | FormData | string | Buffer = {},
qs: IDataObject = {},
2024-06-25 04:32:45 -07:00
url: string = '',
2023-10-25 17:20:43 -07:00
option: IDataObject = {},
): Promise<any> {
const credentials = await this.getCredentials('seaTableApi');
ctx.credentials = credentials as unknown as ICredential;
await getBaseAccessToken.call(this, ctx);
// some API endpoints require the api_token instead of base_access_token.
const token =
endpoint.indexOf('/api/v2.1/dtable/app-download-link/') === 0 ||
endpoint == '/api/v2.1/dtable/app-upload-link/' ||
endpoint.indexOf('/seafhttp/upload-api') === 0
? `${ctx?.credentials?.token}`
: `${ctx?.base?.access_token}`;
2024-06-25 04:32:45 -07:00
let options: IRequestOptions = {
2023-10-25 17:20:43 -07:00
uri: url || `${resolveBaseUri(ctx)}${endpointCtxExpr(ctx, endpoint)}`,
headers: {
Authorization: `Token ${token}`,
},
method,
qs,
body,
json: true,
};
if (Object.keys(option).length !== 0) {
options = Object.assign({}, options, option);
}
// remove header from download request.
if (endpoint.indexOf('/seafhttp/files/') === 0) {
delete options.headers;
}
// enhance header for upload request
if (endpoint.indexOf('/seafhttp/upload-api') === 0) {
options.json = true;
options.headers = {
...options.headers,
'Content-Type': 'multipart/form-data',
};
}
// DEBUG-MODE OR API-REQUESTS
//console.log(options);
2023-10-25 17:20:43 -07:00
if (Object.keys(body).length === 0) {
delete options.body;
}
try {
2024-06-26 04:16:20 -07:00
return await this.helpers.requestWithAuthentication.call(this, 'seaTableApi', options);
2023-10-25 17:20:43 -07:00
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export async function getBaseCollaborators(
this: ILoadOptionsFunctions | IExecuteFunctions | IPollFunctions,
): Promise<any> {
2024-06-26 04:16:20 -07:00
const collaboratorsResult: ICollaboratorsResult = await seaTableApiRequest.call(
2023-10-25 17:20:43 -07:00
this,
{},
'GET',
'/dtable-server/api/v1/dtables/{{dtable_uuid}}/related-users/',
);
2024-06-26 04:16:20 -07:00
const collaborators: ICollaborator[] = collaboratorsResult.user_list || [];
2023-10-25 17:20:43 -07:00
return collaborators;
}
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 function simplify_new(row: IRow) {
for (const key of Object.keys(row)) {
if (key.startsWith('_')) delete row[key];
}
return row;
}
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)
2024-06-25 20:21:25 -07:00
.map((s) => s.replace(/\\([\s\S])/gm, (_, $1) => $1));
2023-10-25 17:20:43 -07:00
// INTERNAL: get collaborator info from @auth.local address
function getCollaboratorInfo(
authLocal: string | null | undefined,
collaboratorList: ICollaborator[],
2024-06-26 04:16:20 -07:00
): ICollaborator {
return (
collaboratorList.find((singleCollaborator) => singleCollaborator.email === authLocal) || {
contact_email: 'unknown',
name: 'unkown',
email: 'unknown',
}
);
2023-10-25 17:20:43 -07:00
}
// INTERNAL: split asset path.
function getAssetPath(type: string, url: string) {
const parts = url.split(`/${type}/`);
if (parts[1]) {
return '/' + type + '/' + parts[1];
}
return url;
}
export function enrichColumns(
row: IRow,
metadata: IDtableMetadataColumn[],
collaboratorList: ICollaborator[],
): IRow {
Object.keys(row).forEach((key) => {
2024-06-26 04:16:20 -07:00
const columnDef = metadata.find((obj) => obj.name === key || obj.key === key);
2023-10-25 17:20:43 -07:00
if (columnDef?.type === 'collaborator') {
// collaborator is an array of strings.
2024-06-26 04:16:20 -07:00
const collaborators = (row[key] as string[]) || [];
2023-10-25 17:20:43 -07:00
if (collaborators.length > 0) {
2024-06-26 04:16:20 -07:00
const newArray = collaborators.map((email) => {
const collaboratorDetails = getCollaboratorInfo(email, collaboratorList);
const newColl = {
email,
contact_email: collaboratorDetails.contact_email,
name: collaboratorDetails.name,
2023-10-25 17:20:43 -07:00
};
return newColl;
});
row[key] = newArray;
}
}
if (
columnDef?.type === 'last-modifier' ||
columnDef?.type === 'creator' ||
columnDef?.key === '_creator' ||
columnDef?.key === '_last_modifier'
) {
// creator or last-modifier are always a single string.
2024-06-26 04:16:20 -07:00
const collaboratorDetails = getCollaboratorInfo(row[key] as string, collaboratorList);
2023-10-25 17:20:43 -07:00
row[key] = {
email: row[key],
2024-06-26 04:16:20 -07:00
contact_email: collaboratorDetails.contact_email,
name: collaboratorDetails.name,
2023-10-25 17:20:43 -07:00
};
}
if (columnDef?.type === 'image') {
2024-06-26 04:16:20 -07:00
const pictures = (row[key] as string[]) || [];
2023-10-25 17:20:43 -07:00
if (pictures.length > 0) {
2024-06-26 04:16:20 -07:00
const newArray = pictures.map((url) => ({
2023-10-25 17:20:43 -07:00
name: url.split('/').pop(),
size: 0,
type: 'image',
2024-06-26 04:16:20 -07:00
url,
2023-10-25 17:20:43 -07:00
path: getAssetPath('images', url),
}));
row[key] = newArray;
}
}
if (columnDef?.type === 'file') {
2024-06-26 04:16:20 -07:00
const files = (row[key] as IFile[]) || [];
2023-10-25 17:20:43 -07:00
files.forEach((file) => {
file.path = getAssetPath('files', file.url);
});
}
if (columnDef?.type === 'digital-sign') {
2024-06-26 04:16:20 -07:00
const digitalSignature: IColumnDigitalSignature | any = row[key];
const collaboratorDetails = getCollaboratorInfo(digitalSignature?.username, collaboratorList);
2023-10-25 17:20:43 -07:00
if (digitalSignature?.username) {
2024-06-26 04:16:20 -07:00
digitalSignature.contact_email = collaboratorDetails.contact_email;
digitalSignature.name = collaboratorDetails.name;
2023-10-25 17:20:43 -07:00
}
}
if (columnDef?.type === 'button') {
delete row[key];
}
});
return row;
}
// using create, I input a string like a5adebe279e04415a28b2c7e256e9e8d@auth.local and it should be transformed to an array.
// same with multi-select.
export function splitStringColumnsToArrays(
row: IRowObject,
columns: TDtableMetadataColumns,
): IRowObject {
columns.map((column) => {
if (column.type == 'collaborator' || column.type == 'multiple-select') {
if (typeof row[column.name] === 'string') {
const input = row[column.name] as string;
row[column.name] = input.split(',').map((item) => item.trim());
}
}
if (column.type == 'number') {
if (typeof row[column.name] === 'string') {
const input = row[column.name] as string;
row[column.name] = parseFloat(input);
}
}
if (column.type == 'rate' || column.type == 'duration') {
if (typeof row[column.name] === 'string') {
const input = row[column.name] as string;
row[column.name] = parseInt(input);
}
}
if (column.type == 'checkbox') {
if (typeof row[column.name] === 'string') {
const input = row[column.name] as string;
row[column.name] = false;
if (input === 'true' || input === 'on' || input === '1') {
row[column.name] = true;
}
}
}
if (column.type == 'date') {
if (typeof row[column.name] === 'string') {
const input = row[column.name] as string;
row[column.name] = moment(input, 'YYYY-mm-dd', true);
}
}
2023-10-25 17:20:43 -07:00
});
return row;
}
2024-06-25 04:32:45 -07:00
// remove nonUpdateColumnTypes and only use allowed columns!
2023-10-25 17:20:43 -07:00
export function rowExport(row: IRowObject, columns: TDtableMetadataColumns): IRowObject {
2024-06-26 04:16:20 -07:00
const rowAllowed = {} as IRowObject;
2023-10-25 17:20:43 -07:00
columns.map((column) => {
if (row[column.name]) {
rowAllowed[column.name] = row[column.name];
}
});
return rowAllowed;
}
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);