Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, objectDropdown, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const cloneObject = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'clone_object',
|
||||
displayName: 'Clone Object',
|
||||
description: 'Copy / clone an object (file) to a new location (same or different bucket), optionally overriding metadata. Perfect for duplicating files as backup or version.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
sourceBucket: bucketDropdown,
|
||||
sourceObject: objectDropdown('sourceBucket'),
|
||||
destBucket: bucketDropdown,
|
||||
destObject: Property.ShortText({
|
||||
displayName: 'Destination Object Name',
|
||||
description: 'Name for the copied object (must be valid object name)',
|
||||
required: true,
|
||||
}),
|
||||
metadataOverrides: Property.Object({
|
||||
displayName: 'Metadata Overrides',
|
||||
description: 'Optional metadata and properties to override in the copied object (contentType, cacheControl, contentDisposition, etc.)',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { sourceBucket, sourceObject, destBucket, destObject, metadataOverrides } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const body = metadataOverrides || undefined;
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.POST,
|
||||
`/b/${sourceBucket}/o/${encodeURIComponent(sourceObject as string)}/copyTo/b/${destBucket}/o/${encodeURIComponent(destObject as string)}`,
|
||||
auth.access_token,
|
||||
body
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
source: {
|
||||
bucket: sourceBucket,
|
||||
object: sourceObject,
|
||||
},
|
||||
destination: {
|
||||
bucket: destBucket,
|
||||
object: destObject,
|
||||
},
|
||||
object: response,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Source object "${sourceObject}" not found in bucket "${sourceBucket}".`);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. Check permissions for source and destination buckets.');
|
||||
}
|
||||
if (error.response?.status === 412) {
|
||||
throw new Error('Precondition failed. Source object may have been modified.');
|
||||
}
|
||||
if (error.response?.status === 409) {
|
||||
throw new Error(`Destination object "${destObject}" already exists in bucket "${destBucket}".`);
|
||||
}
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Invalid request. Check object names and bucket configurations.');
|
||||
}
|
||||
throw new Error(`Failed to clone object: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, aclEntityProperty, bucketAclRoleProperty, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const createBucketAcl = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'create_bucket_acl',
|
||||
displayName: 'Create Bucket ACL',
|
||||
description: 'Add an ACL entry at bucket level. Perfect for granting permission to manage the bucket.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
entity: aclEntityProperty,
|
||||
role: bucketAclRoleProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, entity, role } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const aclEntry = {
|
||||
entity,
|
||||
role,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.POST,
|
||||
`/b/${bucket}/acl`,
|
||||
auth.access_token,
|
||||
aclEntry
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
acl: response,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Bad request. This bucket may have uniform bucket-level access enabled, which doesn\'t support bucket ACLs. Use IAM policies instead.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.buckets.update permission to modify bucket ACLs.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Bucket "${bucket}" not found.`);
|
||||
}
|
||||
if (error.response?.status === 409) {
|
||||
throw new Error(`ACL entry for entity "${entity}" already exists on bucket "${bucket}".`);
|
||||
}
|
||||
throw new Error(`Failed to create bucket ACL: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, aclEntityProperty, objectAclRoleProperty, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const createBucketDefaultObjectAcl = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'create_bucket_default_object_acl',
|
||||
displayName: 'Create Bucket Default Object ACL',
|
||||
description: 'Set default ACLs for new objects added to a bucket. Perfect for automatically assigning permissions to new uploads.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
entity: aclEntityProperty,
|
||||
role: objectAclRoleProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, entity, role } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const aclEntry = {
|
||||
entity,
|
||||
role,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.POST,
|
||||
`/b/${bucket}/defaultObjectAcl`,
|
||||
auth.access_token,
|
||||
aclEntry
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
acl: response,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Bad request. This bucket may have uniform bucket-level access enabled, which doesn\'t support default object ACLs. Use IAM policies instead.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.buckets.update permission to modify bucket default object ACLs.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Bucket "${bucket}" not found.`);
|
||||
}
|
||||
if (error.response?.status === 409) {
|
||||
throw new Error(`Default object ACL entry for entity "${entity}" already exists on bucket "${bucket}".`);
|
||||
}
|
||||
throw new Error(`Failed to create bucket default object ACL: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { projectIdProperty, bucketNameProperty, locationProperty, storageClassProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const createBucket = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'create_bucket',
|
||||
displayName: 'Create Bucket',
|
||||
description: 'Create a new bucket in a specified location/configuration. Perfect for automating storage provisioning for new projects.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
name: bucketNameProperty,
|
||||
location: locationProperty,
|
||||
storageClass: storageClassProperty,
|
||||
versioning: Property.Checkbox({
|
||||
displayName: 'Enable Versioning',
|
||||
required: false,
|
||||
}),
|
||||
uniformBucketLevelAccess: Property.Checkbox({
|
||||
displayName: 'Uniform Bucket Level Access',
|
||||
required: false,
|
||||
}),
|
||||
labels: Property.Object({
|
||||
displayName: 'Labels',
|
||||
description: 'Key-value pairs for bucket labels',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { projectId, name, location, storageClass, versioning, uniformBucketLevelAccess, labels } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const bucketConfig: any = {
|
||||
name,
|
||||
location: location || 'US',
|
||||
storageClass: storageClass || 'STANDARD',
|
||||
};
|
||||
|
||||
if (versioning) {
|
||||
bucketConfig.versioning = { enabled: true };
|
||||
}
|
||||
|
||||
if (uniformBucketLevelAccess !== undefined) {
|
||||
bucketConfig.iamConfiguration = {
|
||||
uniformBucketLevelAccess: {
|
||||
enabled: uniformBucketLevelAccess,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (labels) {
|
||||
bucketConfig.labels = labels;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(HttpMethod.POST, `/b?project=${projectId}`, auth.access_token, bucketConfig);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 409) {
|
||||
throw new Error(`Bucket "${name}" already exists. Bucket names must be globally unique.`);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. Check your permissions for the selected project.');
|
||||
}
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Invalid bucket configuration. Check bucket name format and project settings.');
|
||||
}
|
||||
throw new Error(`Failed to create bucket: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
createAction,
|
||||
Property,
|
||||
OAuth2PropertyValue,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import {
|
||||
bucketDropdown,
|
||||
objectDropdown,
|
||||
aclEntityProperty,
|
||||
objectAclRoleProperty,
|
||||
projectIdProperty,
|
||||
} from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const createObjectAcl = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'create_object_acl',
|
||||
displayName: 'Create Object ACL',
|
||||
description: 'Add an ACL entry to an object (grant a permission). Perfect for granting read/write access to a user or group.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
object: objectDropdown('bucket'),
|
||||
entity: aclEntityProperty,
|
||||
role: objectAclRoleProperty,
|
||||
generation: Property.Number({
|
||||
displayName: 'Generation',
|
||||
description: 'Optional generation number for versioned objects',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, object, entity, role, generation } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const aclEntry = {
|
||||
entity,
|
||||
role,
|
||||
};
|
||||
|
||||
let path = `/b/${bucket}/o/${encodeURIComponent(object as string)}/acl`;
|
||||
if (generation) {
|
||||
path += `?generation=${generation}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.POST,
|
||||
path,
|
||||
auth.access_token,
|
||||
aclEntry
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
object,
|
||||
generation: generation || 'latest',
|
||||
acl: response,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Bad request. This bucket may have uniform bucket-level access enabled, which doesn\'t support object ACLs. Use IAM policies instead.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.objects.setIamPolicy permission or OWNER ACL permission on the object.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Object "${object}" not found in bucket "${bucket}".`);
|
||||
}
|
||||
if (error.response?.status === 409) {
|
||||
throw new Error(`ACL entry for entity "${entity}" already exists on this object.`);
|
||||
}
|
||||
throw new Error(`Failed to create object ACL: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, aclEntityProperty, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const deleteBucketAcl = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'delete_bucket_acl',
|
||||
displayName: 'Delete Bucket ACL',
|
||||
description: 'Remove an ACL entry from a bucket. Perfect for revoking access for a user or group.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
entity: aclEntityProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, entity } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
try {
|
||||
await gcsCommon.makeRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/b/${bucket}/acl/${encodeURIComponent(entity)}`,
|
||||
auth.access_token
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
entity,
|
||||
message: `ACL entry for entity "${entity}" removed successfully from bucket "${bucket}"`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Bad request. This bucket may have uniform bucket-level access enabled, which doesn\'t support bucket ACLs. Use IAM policies instead.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.buckets.update permission to modify bucket ACLs.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
if (error.response?.data?.error?.message?.includes('ACL')) {
|
||||
throw new Error(`ACL entry for entity "${entity}" not found on bucket "${bucket}".`);
|
||||
}
|
||||
throw new Error(`Bucket "${bucket}" not found.`);
|
||||
}
|
||||
throw new Error(`Failed to delete bucket ACL: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, aclEntityProperty, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const deleteBucketDefaultObjectAcl = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'delete_bucket_default_object_acl',
|
||||
displayName: 'Delete Bucket Default Object ACL',
|
||||
description: 'Remove default ACL settings from a bucket. Perfect for reverting to default behavior.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
entity: aclEntityProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, entity } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
try {
|
||||
await gcsCommon.makeRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/b/${bucket}/defaultObjectAcl/${encodeURIComponent(entity)}`,
|
||||
auth.access_token
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
entity,
|
||||
message: `Default object ACL entry for entity "${entity}" removed successfully from bucket "${bucket}"`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Bad request. This bucket may have uniform bucket-level access enabled, which doesn\'t support default object ACLs. Use IAM policies instead.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.buckets.update permission to modify bucket default object ACLs.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
if (error.response?.data?.error?.message?.includes('ACL')) {
|
||||
throw new Error(`Default object ACL entry for entity "${entity}" not found on bucket "${bucket}".`);
|
||||
}
|
||||
throw new Error(`Bucket "${bucket}" not found.`);
|
||||
}
|
||||
throw new Error(`Failed to delete bucket default object ACL: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { createAction, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const deleteEmptyBucket = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'delete_empty_bucket',
|
||||
displayName: 'Delete Empty Bucket',
|
||||
description: 'Clean up unused buckets by deleting them if they contain no live objects.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
try {
|
||||
await gcsCommon.makeRequest(HttpMethod.DELETE, `/b/${bucket}`, auth.access_token);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Bucket "${bucket}" deleted successfully`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 409) {
|
||||
throw new Error(`Bucket "${bucket}" contains live or noncurrent objects and cannot be deleted. Remove all objects first.`);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.buckets.delete permission to delete this bucket.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Bucket "${bucket}" not found. It may have already been deleted.`);
|
||||
}
|
||||
throw new Error(`Failed to delete bucket: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, objectDropdown, aclEntityProperty, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const deleteObjectAcl = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'delete_object_acl',
|
||||
displayName: 'Delete Object ACL',
|
||||
description: 'Remove an ACL entry from an object. Perfect for revoking access for a user.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
object: objectDropdown('bucket'),
|
||||
entity: aclEntityProperty,
|
||||
generation: Property.Number({
|
||||
displayName: 'Generation',
|
||||
description: 'Optional generation number for versioned objects',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, object, entity, generation } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
let path = `/b/${bucket}/o/${encodeURIComponent(object as string)}/acl/${encodeURIComponent(entity as string)}`;
|
||||
if (generation) {
|
||||
path += `?generation=${generation}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await gcsCommon.makeRequest(HttpMethod.DELETE, path, auth.access_token);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
object,
|
||||
entity,
|
||||
generation: generation || 'latest',
|
||||
message: `ACL entry for entity "${entity}" removed successfully from object "${object}"`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
if (entity.includes('user-') && entity.includes('@')) {
|
||||
throw new Error('Cannot remove OWNER access from the object owner. The only way to remove owner access is to delete or replace the object.');
|
||||
}
|
||||
throw new Error('Bad request. This bucket may have uniform bucket-level access enabled, which doesn\'t support object ACLs. Use IAM policies instead.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.objects.setIamPolicy permission or OWNER ACL permission on the object.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
if (error.response?.data?.error?.message?.includes('ACL')) {
|
||||
throw new Error(`ACL entry for entity "${entity}" not found on object "${object}".`);
|
||||
}
|
||||
throw new Error(`Object "${object}" not found in bucket "${bucket}".`);
|
||||
}
|
||||
throw new Error(`Failed to delete object ACL: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, objectDropdown, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const deleteObject = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'delete_object',
|
||||
displayName: 'Delete Object',
|
||||
description: 'Permanently delete a specific object. Perfect for removing obsolete files.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
object: objectDropdown('bucket'),
|
||||
generation: Property.Number({
|
||||
displayName: 'Generation',
|
||||
description: 'Optional generation number for versioned objects (permanently deletes a specific revision)',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { bucket, object, generation } = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
let path = `/b/${bucket}/o/${encodeURIComponent(object as string)}`;
|
||||
if (generation) {
|
||||
path += `?generation=${generation}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await gcsCommon.makeRequest(HttpMethod.DELETE, path, auth.access_token);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
object,
|
||||
generation: generation || 'latest',
|
||||
message: `Object "${object}" deleted successfully from bucket "${bucket}"`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Object "${object}" not found in bucket "${bucket}". It may have already been deleted.`);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.objects.delete permission to delete this object.');
|
||||
}
|
||||
if (error.response?.status === 412) {
|
||||
throw new Error('Precondition failed. Object may have been modified or deleted by another process.');
|
||||
}
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Invalid request. Check object name and generation parameters.');
|
||||
}
|
||||
throw new Error(`Failed to delete object: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const searchBuckets = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'search_buckets',
|
||||
displayName: 'Search Buckets',
|
||||
description: 'Search buckets by name. Perfect for finding buckets in your project.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
prefix: Property.ShortText({
|
||||
displayName: 'Name Prefix',
|
||||
description: 'Filter buckets whose names begin with this prefix',
|
||||
required: false,
|
||||
}),
|
||||
includeSoftDeleted: Property.Checkbox({
|
||||
displayName: 'Include Soft-Deleted',
|
||||
description: 'Include soft-deleted bucket versions in results',
|
||||
required: false,
|
||||
}),
|
||||
projection: Property.StaticDropdown({
|
||||
displayName: 'Projection',
|
||||
description: 'Set of properties to return',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'No ACLs (faster)', value: 'noAcl' },
|
||||
{ label: 'Full (includes ACLs)', value: 'full' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
pageToken: Property.ShortText({
|
||||
displayName: 'Page Token',
|
||||
description: 'Token for pagination (from previous response)',
|
||||
required: false,
|
||||
}),
|
||||
maxResults: Property.Number({
|
||||
displayName: 'Max Results',
|
||||
description: 'Maximum number of buckets to return',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
projectId,
|
||||
prefix,
|
||||
includeSoftDeleted,
|
||||
projection,
|
||||
pageToken,
|
||||
maxResults
|
||||
} = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('project', projectId!);
|
||||
if (prefix) params.append('prefix', prefix);
|
||||
if (includeSoftDeleted) params.append('softDeleted', 'true');
|
||||
if (projection) params.append('projection', projection);
|
||||
if (pageToken) params.append('pageToken', pageToken);
|
||||
if (maxResults) params.append('maxResults', maxResults.toString());
|
||||
|
||||
const path = `/b?${params.toString()}`;
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(HttpMethod.GET, path, auth.access_token);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
projectId,
|
||||
items: response.items || [],
|
||||
nextPageToken: response.nextPageToken,
|
||||
totalBuckets: response.items?.length || 0,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.buckets.list permission to search buckets in this project.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Project "${projectId}" not found or you don't have access to it.`);
|
||||
}
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Invalid search parameters. Check your project ID and other filters.');
|
||||
}
|
||||
throw new Error(`Failed to search buckets: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
export const searchObjects = createAction({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'search_objects',
|
||||
displayName: 'Search Objects',
|
||||
description: 'Search objects by criteria. Perfect for finding files in your bucket.',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
prefix: Property.ShortText({
|
||||
displayName: 'Prefix',
|
||||
description: 'Filter objects whose names begin with this prefix',
|
||||
required: false,
|
||||
}),
|
||||
matchGlob: Property.ShortText({
|
||||
displayName: 'Glob Pattern',
|
||||
description: 'Glob pattern to filter results (e.g., "folder/*", "backup-*.txt")',
|
||||
required: false,
|
||||
}),
|
||||
delimiter: Property.ShortText({
|
||||
displayName: 'Delimiter',
|
||||
description: 'Delimiter for hierarchical listing (commonly "/")',
|
||||
required: false,
|
||||
}),
|
||||
includeFoldersAsPrefixes: Property.Checkbox({
|
||||
displayName: 'Include Folders',
|
||||
description: 'Include empty folders and managed folders in results',
|
||||
required: false,
|
||||
}),
|
||||
versions: Property.Checkbox({
|
||||
displayName: 'Include Versions',
|
||||
description: 'List all versions of objects as distinct results',
|
||||
required: false,
|
||||
}),
|
||||
pageToken: Property.ShortText({
|
||||
displayName: 'Page Token',
|
||||
description: 'Token for pagination (from previous response)',
|
||||
required: false,
|
||||
}),
|
||||
maxResults: Property.Number({
|
||||
displayName: 'Max Results',
|
||||
description: 'Maximum number of objects to return (recommended: ≤1000)',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
bucket,
|
||||
prefix,
|
||||
matchGlob,
|
||||
delimiter,
|
||||
includeFoldersAsPrefixes,
|
||||
versions,
|
||||
pageToken,
|
||||
maxResults
|
||||
} = context.propsValue;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (prefix) params.append('prefix', prefix);
|
||||
if (matchGlob) params.append('matchGlob', matchGlob);
|
||||
if (delimiter) params.append('delimiter', delimiter);
|
||||
if (includeFoldersAsPrefixes) params.append('includeFoldersAsPrefixes', 'true');
|
||||
if (versions) params.append('versions', 'true');
|
||||
if (pageToken) params.append('pageToken', pageToken);
|
||||
if (maxResults) params.append('maxResults', maxResults.toString());
|
||||
|
||||
const path = `/b/${bucket}/o?${params.toString()}`;
|
||||
|
||||
try {
|
||||
const response = await gcsCommon.makeRequest(HttpMethod.GET, path, auth.access_token);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
bucket,
|
||||
items: response.items || [],
|
||||
nextPageToken: response.nextPageToken,
|
||||
prefixes: response.prefixes || [],
|
||||
totalItems: response.items?.length || 0,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error('Access denied. You need storage.objects.list permission to search objects in this bucket.');
|
||||
}
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(`Bucket "${bucket}" not found.`);
|
||||
}
|
||||
if (error.response?.status === 400) {
|
||||
throw new Error('Invalid search parameters. Check your prefix, glob pattern, or other filters.');
|
||||
}
|
||||
throw new Error(`Failed to search objects: ${error.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { PieceAuth } from '@activepieces/pieces-framework';
|
||||
|
||||
export const googleCloudStorageAuth = PieceAuth.OAuth2({
|
||||
description: '',
|
||||
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
||||
tokenUrl: 'https://oauth2.googleapis.com/token',
|
||||
required: true,
|
||||
scope: [
|
||||
'https://www.googleapis.com/auth/devstorage.read_write',
|
||||
'https://www.googleapis.com/auth/devstorage.full_control',
|
||||
'https://www.googleapis.com/auth/cloud-platform.read-only',
|
||||
'https://www.googleapis.com/auth/pubsub'
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
AuthenticationType,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
|
||||
export const gcsCommon = {
|
||||
gcsBaseUrl: 'https://www.googleapis.com/storage/v1',
|
||||
pubsubBaseUrl: 'https://pubsub.googleapis.com/v1',
|
||||
|
||||
async makeGCSRequest(
|
||||
method: HttpMethod,
|
||||
path: string,
|
||||
accessToken: string,
|
||||
body?: any
|
||||
): Promise<any> {
|
||||
const url = path.startsWith('http') ? path : `${this.gcsBaseUrl}${path}`;
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method,
|
||||
url,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: accessToken,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
|
||||
async makePubSubRequest(
|
||||
method: HttpMethod,
|
||||
path: string,
|
||||
accessToken: string,
|
||||
body?: any
|
||||
): Promise<any> {
|
||||
const url = path.startsWith('http') ? path : `${this.pubsubBaseUrl}${path}`;
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method,
|
||||
url,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: accessToken,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
return response.body;
|
||||
},
|
||||
|
||||
// For backward compatibility
|
||||
async makeRequest(
|
||||
method: HttpMethod,
|
||||
path: string,
|
||||
accessToken: string,
|
||||
body?: any
|
||||
): Promise<any> {
|
||||
return this.makeGCSRequest(method, path, accessToken, body);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,249 @@
|
||||
import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { gcsCommon } from './client';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { googleCloudStorageAuth } from './auth';
|
||||
|
||||
export const bucketDropdown = Property.Dropdown<string,true,typeof googleCloudStorageAuth>({
|
||||
displayName: 'Bucket',
|
||||
required: true,
|
||||
refreshers: ['projectId'],
|
||||
auth: googleCloudStorageAuth,
|
||||
options: async ({ auth, projectId }) => {
|
||||
if (!auth || !projectId) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: projectId
|
||||
? 'Please connect your account first'
|
||||
: 'Please enter project ID first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const authValue = auth as OAuth2PropertyValue;
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.GET,
|
||||
`/b?project=${projectId}`,
|
||||
authValue.access_token
|
||||
);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options:
|
||||
response.items?.map((bucket: any) => ({
|
||||
label: bucket.name,
|
||||
value: bucket.name,
|
||||
})) || [],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Failed to load buckets',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const objectDropdown = (bucketProperty: string) =>
|
||||
Property.Dropdown<string,true,typeof googleCloudStorageAuth>({
|
||||
displayName: 'Object',
|
||||
required: true,
|
||||
auth: googleCloudStorageAuth,
|
||||
refreshers: [bucketProperty],
|
||||
options: async ({ auth, [bucketProperty]: bucket }) => {
|
||||
if (!auth || !bucket) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: bucket
|
||||
? 'Please connect your account first'
|
||||
: 'Please select a bucket first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const authValue = auth as OAuth2PropertyValue;
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.GET,
|
||||
`/b/${bucket}/o?maxResults=100`,
|
||||
authValue.access_token
|
||||
);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options:
|
||||
response.items?.map((object: any) => ({
|
||||
label: object.name,
|
||||
value: object.name,
|
||||
})) || [],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Failed to load objects',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const projectIdProperty = Property.Dropdown<string,true,typeof googleCloudStorageAuth>({
|
||||
displayName: 'Project',
|
||||
required: true,
|
||||
auth: googleCloudStorageAuth,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const authValue = auth as OAuth2PropertyValue;
|
||||
// Use Google Cloud Resource Manager API to list projects
|
||||
const response = await gcsCommon.makeRequest(
|
||||
HttpMethod.GET,
|
||||
'https://cloudresourcemanager.googleapis.com/v1/projects?filter=lifecycleState:ACTIVE',
|
||||
authValue.access_token
|
||||
);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: response.projects?.map((project: any) => ({
|
||||
label: `${project.displayName || project.name} (${project.projectId})`,
|
||||
value: project.projectId,
|
||||
})) || [],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Failed to load projects. Check your permissions.',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const bucketNameProperty = Property.ShortText({
|
||||
displayName: 'Bucket Name',
|
||||
description: 'Unique name for your bucket (must be globally unique, 3-63 characters)',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const objectNameProperty = Property.ShortText({
|
||||
displayName: 'Object Name',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const locationProperty = Property.StaticDropdown({
|
||||
displayName: 'Location',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
// Multi-region
|
||||
{ label: 'US (Multi-region)', value: 'US' },
|
||||
{ label: 'EU (Multi-region)', value: 'EU' },
|
||||
{ label: 'ASIA (Multi-region)', value: 'ASIA' },
|
||||
// US regions
|
||||
{ label: 'us-central1 (Iowa)', value: 'us-central1' },
|
||||
{ label: 'us-east1 (South Carolina)', value: 'us-east1' },
|
||||
{ label: 'us-east4 (Northern Virginia)', value: 'us-east4' },
|
||||
{ label: 'us-west1 (Oregon)', value: 'us-west1' },
|
||||
{ label: 'us-west2 (Los Angeles)', value: 'us-west2' },
|
||||
{ label: 'us-west3 (Salt Lake City)', value: 'us-west3' },
|
||||
{ label: 'us-west4 (Las Vegas)', value: 'us-west4' },
|
||||
{ label: 'us-south1 (Dallas)', value: 'us-south1' },
|
||||
// Europe regions
|
||||
{ label: 'europe-central2 (Warsaw)', value: 'europe-central2' },
|
||||
{ label: 'europe-north1 (Finland)', value: 'europe-north1' },
|
||||
{ label: 'europe-southwest1 (Madrid)', value: 'europe-southwest1' },
|
||||
{ label: 'europe-west1 (Belgium)', value: 'europe-west1' },
|
||||
{ label: 'europe-west2 (London)', value: 'europe-west2' },
|
||||
{ label: 'europe-west3 (Frankfurt)', value: 'europe-west3' },
|
||||
{ label: 'europe-west4 (Netherlands)', value: 'europe-west4' },
|
||||
{ label: 'europe-west6 (Zurich)', value: 'europe-west6' },
|
||||
{ label: 'europe-west8 (Milan)', value: 'europe-west8' },
|
||||
{ label: 'europe-west9 (Paris)', value: 'europe-west9' },
|
||||
// Asia regions
|
||||
{ label: 'asia-east1 (Taiwan)', value: 'asia-east1' },
|
||||
{ label: 'asia-east2 (Hong Kong)', value: 'asia-east2' },
|
||||
{ label: 'asia-northeast1 (Tokyo)', value: 'asia-northeast1' },
|
||||
{ label: 'asia-northeast2 (Osaka)', value: 'asia-northeast2' },
|
||||
{ label: 'asia-northeast3 (Seoul)', value: 'asia-northeast3' },
|
||||
{ label: 'asia-south1 (Mumbai)', value: 'asia-south1' },
|
||||
{ label: 'asia-south2 (Delhi)', value: 'asia-south2' },
|
||||
{ label: 'asia-southeast1 (Singapore)', value: 'asia-southeast1' },
|
||||
{ label: 'asia-southeast2 (Jakarta)', value: 'asia-southeast2' },
|
||||
// Other regions
|
||||
{ label: 'australia-southeast1 (Sydney)', value: 'australia-southeast1' },
|
||||
{ label: 'australia-southeast2 (Melbourne)', value: 'australia-southeast2' },
|
||||
{ label: 'northamerica-northeast1 (Montreal)', value: 'northamerica-northeast1' },
|
||||
{ label: 'northamerica-northeast2 (Toronto)', value: 'northamerica-northeast2' },
|
||||
{ label: 'southamerica-east1 (São Paulo)', value: 'southamerica-east1' },
|
||||
{ label: 'southamerica-west1 (Santiago)', value: 'southamerica-west1' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const storageClassProperty = Property.StaticDropdown({
|
||||
displayName: 'Storage Class',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Standard', value: 'STANDARD' },
|
||||
{ label: 'Nearline', value: 'NEARLINE' },
|
||||
{ label: 'Coldline', value: 'COLDLINE' },
|
||||
{ label: 'Archive', value: 'ARCHIVE' },
|
||||
{ label: 'Multi-regional', value: 'MULTI_REGIONAL' },
|
||||
{ label: 'Regional', value: 'REGIONAL' },
|
||||
{ label: 'Durable Reduced Availability', value: 'DURABLE_REDUCED_AVAILABILITY' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const aclEntityProperty = Property.ShortText({
|
||||
displayName: 'Entity',
|
||||
description: 'The entity to grant access to. Must include the entity type prefix. Format: user-emailAddress, group-groupId, group-emailAddress, domain-domainName, project-team-projectId, allUsers, or allAuthenticatedUsers. Examples: user-liz@example.com, group-mygroup@googlegroups.com, domain-example.com, allUsers',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const aclRoleProperty = Property.StaticDropdown({
|
||||
displayName: 'Role',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Reader', value: 'READER' },
|
||||
{ label: 'Writer', value: 'WRITER' },
|
||||
{ label: 'Owner', value: 'OWNER' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// For bucket ACLs - supports OWNER, WRITER, READER
|
||||
export const bucketAclRoleProperty = Property.StaticDropdown({
|
||||
displayName: 'Role',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Reader', value: 'READER' },
|
||||
{ label: 'Writer', value: 'WRITER' },
|
||||
{ label: 'Owner', value: 'OWNER' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// For object ACLs - supports OWNER, READER only
|
||||
export const objectAclRoleProperty = Property.StaticDropdown({
|
||||
displayName: 'Role',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Reader', value: 'READER' },
|
||||
{ label: 'Owner', value: 'OWNER' },
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
interface TriggerData {
|
||||
topicName: string;
|
||||
subscriptionName: string;
|
||||
notificationId: string;
|
||||
}
|
||||
|
||||
export const newObjectCreated = createTrigger({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'new_object_created',
|
||||
displayName: 'New Object Created',
|
||||
description: 'Triggers when a new object is created in a bucket',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
prefix: Property.ShortText({
|
||||
displayName: 'Prefix Filter',
|
||||
description: 'Only trigger for objects with this prefix',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
kind: 'storage#object',
|
||||
id: 'example-bucket/example-object/1234567890',
|
||||
name: 'example-object.txt',
|
||||
bucket: 'example-bucket',
|
||||
generation: '1234567890',
|
||||
contentType: 'text/plain',
|
||||
timeCreated: '2023-01-01T00:00:00.000Z',
|
||||
updated: '2023-01-01T00:00:00.000Z',
|
||||
size: '1024',
|
||||
},
|
||||
onEnable: async (context) => {
|
||||
const { projectId, bucket, prefix } = context.propsValue;
|
||||
const auth = context.auth;
|
||||
|
||||
// Generate unique names for this trigger instance
|
||||
const triggerId = `ap-gcs-${bucket}-${Date.now()}`;
|
||||
const topicName = `projects/${projectId}/topics/${triggerId}`;
|
||||
const subscriptionName = `projects/${projectId}/subscriptions/${triggerId}`;
|
||||
|
||||
try {
|
||||
// 1. Create Pub/Sub topic
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.PUT,
|
||||
`/projects/${projectId}/topics/${triggerId}`,
|
||||
auth.access_token
|
||||
);
|
||||
|
||||
// 2. Create GCS notification configuration
|
||||
const notificationConfig: any = {
|
||||
topic: topicName,
|
||||
payload_format: 'JSON_API_V1',
|
||||
event_types: ['OBJECT_FINALIZE'],
|
||||
};
|
||||
|
||||
if (prefix) {
|
||||
notificationConfig.object_name_prefix = prefix;
|
||||
}
|
||||
|
||||
const notificationResponse = await gcsCommon.makeGCSRequest(
|
||||
HttpMethod.POST,
|
||||
`/b/${bucket}/notificationConfigs`,
|
||||
auth.access_token,
|
||||
notificationConfig
|
||||
);
|
||||
|
||||
// 3. Create Pub/Sub subscription with webhook push
|
||||
const subscriptionConfig = {
|
||||
topic: topicName,
|
||||
pushConfig: {
|
||||
pushEndpoint: context.webhookUrl,
|
||||
},
|
||||
ackDeadlineSeconds: 60,
|
||||
};
|
||||
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.PUT,
|
||||
`/projects/${projectId}/subscriptions/${triggerId}`,
|
||||
auth.access_token,
|
||||
subscriptionConfig
|
||||
);
|
||||
|
||||
// Store trigger data for cleanup
|
||||
await context.store.put<TriggerData>('_trigger', {
|
||||
topicName: triggerId,
|
||||
subscriptionName: triggerId,
|
||||
notificationId: notificationResponse.id,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// Cleanup on failure
|
||||
try {
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/subscriptions/${triggerId}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/topics/${triggerId}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
throw new Error(`Failed to setup Pub/Sub notifications: ${(error as any)?.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
onDisable: async (context) => {
|
||||
const triggerData = await context.store.get<TriggerData>('_trigger');
|
||||
if (!triggerData) return;
|
||||
|
||||
const { projectId } = context.propsValue;
|
||||
const { bucket } = context.propsValue;
|
||||
const auth = context.auth;
|
||||
|
||||
// Clean up in reverse order
|
||||
try {
|
||||
// Delete subscription
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/subscriptions/${triggerData.subscriptionName}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
// Delete notification config
|
||||
await gcsCommon.makeGCSRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/b/${bucket}/notificationConfigs/${triggerData.notificationId}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
// Delete topic
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/topics/${triggerData.topicName}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
run: async (context) => {
|
||||
const payload = context.payload.body as any;
|
||||
|
||||
if (!payload?.message?.data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Decode Pub/Sub message
|
||||
const messageData = JSON.parse(
|
||||
Buffer.from(payload.message.data, 'base64').toString()
|
||||
);
|
||||
|
||||
// Extract GCS object from notification payload
|
||||
const gcsObject = messageData;
|
||||
|
||||
return [gcsObject];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework';
|
||||
import { googleCloudStorageAuth } from '../common/auth';
|
||||
import { gcsCommon } from '../common/client';
|
||||
import { bucketDropdown, projectIdProperty } from '../common/props';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
interface TriggerData {
|
||||
topicName: string;
|
||||
subscriptionName: string;
|
||||
notificationId: string;
|
||||
}
|
||||
|
||||
export const objectUpdated = createTrigger({
|
||||
auth: googleCloudStorageAuth,
|
||||
name: 'object_updated',
|
||||
displayName: 'Object Updated',
|
||||
description: 'Triggers when an existing object is updated in a bucket',
|
||||
props: {
|
||||
projectId: projectIdProperty,
|
||||
bucket: bucketDropdown,
|
||||
prefix: Property.ShortText({
|
||||
displayName: 'Prefix Filter',
|
||||
description: 'Only trigger for objects with this prefix',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
kind: 'storage#object',
|
||||
id: 'example-bucket/example-object/1234567890',
|
||||
name: 'example-object.txt',
|
||||
bucket: 'example-bucket',
|
||||
generation: '1234567890',
|
||||
contentType: 'text/plain',
|
||||
timeCreated: '2023-01-01T00:00:00.000Z',
|
||||
updated: '2023-01-01T01:00:00.000Z',
|
||||
size: '2048',
|
||||
},
|
||||
onEnable: async (context) => {
|
||||
const { projectId, bucket, prefix } = context.propsValue;
|
||||
const auth = context.auth;
|
||||
|
||||
// Generate unique names for this trigger instance
|
||||
const triggerId = `ap-gcs-update-${bucket}-${Date.now()}`;
|
||||
const topicName = `projects/${projectId}/topics/${triggerId}`;
|
||||
const subscriptionName = `projects/${projectId}/subscriptions/${triggerId}`;
|
||||
|
||||
try {
|
||||
// 1. Create Pub/Sub topic
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.PUT,
|
||||
`/projects/${projectId}/topics/${triggerId}`,
|
||||
auth.access_token
|
||||
);
|
||||
|
||||
// 2. Create GCS notification configuration for metadata updates
|
||||
const notificationConfig: any = {
|
||||
topic: topicName,
|
||||
payload_format: 'JSON_API_V1',
|
||||
event_types: ['OBJECT_METADATA_UPDATE'],
|
||||
};
|
||||
|
||||
if (prefix) {
|
||||
notificationConfig.object_name_prefix = prefix;
|
||||
}
|
||||
|
||||
const notificationResponse = await gcsCommon.makeGCSRequest(
|
||||
HttpMethod.POST,
|
||||
`/b/${bucket}/notificationConfigs`,
|
||||
auth.access_token,
|
||||
notificationConfig
|
||||
);
|
||||
|
||||
// 3. Create Pub/Sub subscription with webhook push
|
||||
const subscriptionConfig = {
|
||||
topic: topicName,
|
||||
pushConfig: {
|
||||
pushEndpoint: context.webhookUrl,
|
||||
},
|
||||
ackDeadlineSeconds: 60,
|
||||
};
|
||||
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.PUT,
|
||||
`/projects/${projectId}/subscriptions/${triggerId}`,
|
||||
auth.access_token,
|
||||
subscriptionConfig
|
||||
);
|
||||
|
||||
// Store trigger data for cleanup
|
||||
await context.store.put<TriggerData>('_trigger', {
|
||||
topicName: triggerId,
|
||||
subscriptionName: triggerId,
|
||||
notificationId: notificationResponse.id,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// Cleanup on failure
|
||||
try {
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/subscriptions/${triggerId}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/topics/${triggerId}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
throw new Error(`Failed to setup Pub/Sub notifications: ${(error as any)?.message || 'Unknown error'}`);
|
||||
}
|
||||
},
|
||||
onDisable: async (context) => {
|
||||
const triggerData = await context.store.get<TriggerData>('_trigger');
|
||||
if (!triggerData) return;
|
||||
|
||||
const { projectId } = context.propsValue;
|
||||
const { bucket } = context.propsValue;
|
||||
const auth = context.auth;
|
||||
|
||||
// Clean up in reverse order
|
||||
try {
|
||||
// Delete subscription
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/subscriptions/${triggerData.subscriptionName}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
// Delete notification config
|
||||
await gcsCommon.makeGCSRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/b/${bucket}/notificationConfigs/${triggerData.notificationId}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
// Delete topic
|
||||
await gcsCommon.makePubSubRequest(
|
||||
HttpMethod.DELETE,
|
||||
`/projects/${projectId}/topics/${triggerData.topicName}`,
|
||||
auth.access_token
|
||||
);
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
run: async (context) => {
|
||||
const payload = context.payload.body as any;
|
||||
|
||||
if (!payload?.message?.data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Decode Pub/Sub message
|
||||
const messageData = JSON.parse(
|
||||
Buffer.from(payload.message.data, 'base64').toString()
|
||||
);
|
||||
|
||||
// Extract GCS object from notification payload
|
||||
const gcsObject = messageData;
|
||||
|
||||
return [gcsObject];
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user