--------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Scott Wilson <[email protected]>
@@ -2285,6 +2285,10 @@ export const AppConnections = {
|
|||||||
},
|
},
|
||||||
CHECKLY: {
|
CHECKLY: {
|
||||||
apiKey: "The API key used to authenticate with Checkly."
|
apiKey: "The API key used to authenticate with Checkly."
|
||||||
|
},
|
||||||
|
SUPABASE: {
|
||||||
|
accessKey: "The Key used to access Supabase.",
|
||||||
|
instanceUrl: "The URL used to access Supabase."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2494,6 +2498,10 @@ export const SecretSyncs = {
|
|||||||
},
|
},
|
||||||
CHECKLY: {
|
CHECKLY: {
|
||||||
accountId: "The ID of the Checkly account to sync secrets to."
|
accountId: "The ID of the Checkly account to sync secrets to."
|
||||||
|
},
|
||||||
|
SUPABASE: {
|
||||||
|
projectId: "The ID of the Supabase project to sync secrets to.",
|
||||||
|
projectName: "The name of the Supabase project to sync secrets to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ import {
|
|||||||
RenderConnectionListItemSchema,
|
RenderConnectionListItemSchema,
|
||||||
SanitizedRenderConnectionSchema
|
SanitizedRenderConnectionSchema
|
||||||
} from "@app/services/app-connection/render/render-connection-schema";
|
} from "@app/services/app-connection/render/render-connection-schema";
|
||||||
|
import {
|
||||||
|
SanitizedSupabaseConnectionSchema,
|
||||||
|
SupabaseConnectionListItemSchema
|
||||||
|
} from "@app/services/app-connection/supabase";
|
||||||
import {
|
import {
|
||||||
SanitizedTeamCityConnectionSchema,
|
SanitizedTeamCityConnectionSchema,
|
||||||
TeamCityConnectionListItemSchema
|
TeamCityConnectionListItemSchema
|
||||||
@@ -133,7 +137,8 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedBitbucketConnectionSchema.options,
|
...SanitizedBitbucketConnectionSchema.options,
|
||||||
...SanitizedZabbixConnectionSchema.options,
|
...SanitizedZabbixConnectionSchema.options,
|
||||||
...SanitizedRailwayConnectionSchema.options,
|
...SanitizedRailwayConnectionSchema.options,
|
||||||
...SanitizedChecklyConnectionSchema.options
|
...SanitizedChecklyConnectionSchema.options,
|
||||||
|
...SanitizedSupabaseConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
@@ -169,7 +174,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
BitbucketConnectionListItemSchema,
|
BitbucketConnectionListItemSchema,
|
||||||
ZabbixConnectionListItemSchema,
|
ZabbixConnectionListItemSchema,
|
||||||
RailwayConnectionListItemSchema,
|
RailwayConnectionListItemSchema,
|
||||||
ChecklyConnectionListItemSchema
|
ChecklyConnectionListItemSchema,
|
||||||
|
SupabaseConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { registerMySqlConnectionRouter } from "./mysql-connection-router";
|
|||||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||||
import { registerRailwayConnectionRouter } from "./railway-connection-router";
|
import { registerRailwayConnectionRouter } from "./railway-connection-router";
|
||||||
import { registerRenderConnectionRouter } from "./render-connection-router";
|
import { registerRenderConnectionRouter } from "./render-connection-router";
|
||||||
|
import { registerSupabaseConnectionRouter } from "./supabase-connection-router";
|
||||||
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
||||||
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
||||||
import { registerVercelConnectionRouter } from "./vercel-connection-router";
|
import { registerVercelConnectionRouter } from "./vercel-connection-router";
|
||||||
@@ -70,5 +71,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Bitbucket]: registerBitbucketConnectionRouter,
|
[AppConnection.Bitbucket]: registerBitbucketConnectionRouter,
|
||||||
[AppConnection.Zabbix]: registerZabbixConnectionRouter,
|
[AppConnection.Zabbix]: registerZabbixConnectionRouter,
|
||||||
[AppConnection.Railway]: registerRailwayConnectionRouter,
|
[AppConnection.Railway]: registerRailwayConnectionRouter,
|
||||||
[AppConnection.Checkly]: registerChecklyConnectionRouter
|
[AppConnection.Checkly]: registerChecklyConnectionRouter,
|
||||||
|
[AppConnection.Supabase]: registerSupabaseConnectionRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateSupabaseConnectionSchema,
|
||||||
|
SanitizedSupabaseConnectionSchema,
|
||||||
|
UpdateSupabaseConnectionSchema
|
||||||
|
} from "@app/services/app-connection/supabase";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerSupabaseConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.Supabase,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedSupabaseConnectionSchema,
|
||||||
|
createSchema: CreateSupabaseConnectionSchema,
|
||||||
|
updateSchema: UpdateSupabaseConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/projects`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
projects: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const projects = await server.services.appConnection.supabase.listProjects(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { projects };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -21,6 +21,7 @@ import { registerHerokuSyncRouter } from "./heroku-sync-router";
|
|||||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
||||||
import { registerRailwaySyncRouter } from "./railway-sync-router";
|
import { registerRailwaySyncRouter } from "./railway-sync-router";
|
||||||
import { registerRenderSyncRouter } from "./render-sync-router";
|
import { registerRenderSyncRouter } from "./render-sync-router";
|
||||||
|
import { registerSupabaseSyncRouter } from "./supabase-sync-router";
|
||||||
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
||||||
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
||||||
import { registerVercelSyncRouter } from "./vercel-sync-router";
|
import { registerVercelSyncRouter } from "./vercel-sync-router";
|
||||||
@@ -53,7 +54,7 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.GitLab]: registerGitLabSyncRouter,
|
[SecretSync.GitLab]: registerGitLabSyncRouter,
|
||||||
[SecretSync.CloudflarePages]: registerCloudflarePagesSyncRouter,
|
[SecretSync.CloudflarePages]: registerCloudflarePagesSyncRouter,
|
||||||
[SecretSync.CloudflareWorkers]: registerCloudflareWorkersSyncRouter,
|
[SecretSync.CloudflareWorkers]: registerCloudflareWorkersSyncRouter,
|
||||||
|
[SecretSync.Supabase]: registerSupabaseSyncRouter,
|
||||||
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
||||||
[SecretSync.Railway]: registerRailwaySyncRouter,
|
[SecretSync.Railway]: registerRailwaySyncRouter,
|
||||||
[SecretSync.Checkly]: registerChecklySyncRouter
|
[SecretSync.Checkly]: registerChecklySyncRouter
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { HerokuSyncListItemSchema, HerokuSyncSchema } from "@app/services/secret
|
|||||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
||||||
import { RailwaySyncListItemSchema, RailwaySyncSchema } from "@app/services/secret-sync/railway/railway-sync-schemas";
|
import { RailwaySyncListItemSchema, RailwaySyncSchema } from "@app/services/secret-sync/railway/railway-sync-schemas";
|
||||||
import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas";
|
import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas";
|
||||||
|
import { SupabaseSyncListItemSchema, SupabaseSyncSchema } from "@app/services/secret-sync/supabase";
|
||||||
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
||||||
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
||||||
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
|
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
|
||||||
@@ -71,7 +72,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
GitLabSyncSchema,
|
GitLabSyncSchema,
|
||||||
CloudflarePagesSyncSchema,
|
CloudflarePagesSyncSchema,
|
||||||
CloudflareWorkersSyncSchema,
|
CloudflareWorkersSyncSchema,
|
||||||
|
SupabaseSyncSchema,
|
||||||
ZabbixSyncSchema,
|
ZabbixSyncSchema,
|
||||||
RailwaySyncSchema,
|
RailwaySyncSchema,
|
||||||
ChecklySyncSchema
|
ChecklySyncSchema
|
||||||
@@ -104,7 +105,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
|
|
||||||
ZabbixSyncListItemSchema,
|
ZabbixSyncListItemSchema,
|
||||||
RailwaySyncListItemSchema,
|
RailwaySyncListItemSchema,
|
||||||
ChecklySyncListItemSchema
|
ChecklySyncListItemSchema,
|
||||||
|
SupabaseSyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
CreateSupabaseSyncSchema,
|
||||||
|
SupabaseSyncSchema,
|
||||||
|
UpdateSupabaseSyncSchema
|
||||||
|
} from "@app/services/secret-sync/supabase";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerSupabaseSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.Supabase,
|
||||||
|
server,
|
||||||
|
responseSchema: SupabaseSyncSchema,
|
||||||
|
createSchema: CreateSupabaseSyncSchema,
|
||||||
|
updateSchema: UpdateSupabaseSyncSchema
|
||||||
|
});
|
||||||
@@ -31,7 +31,8 @@ export enum AppConnection {
|
|||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Bitbucket = "bitbucket",
|
Bitbucket = "bitbucket",
|
||||||
Checkly = "checkly"
|
Checkly = "checkly",
|
||||||
|
Supabase = "supabase"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postg
|
|||||||
import { getRailwayConnectionListItem, validateRailwayConnectionCredentials } from "./railway";
|
import { getRailwayConnectionListItem, validateRailwayConnectionCredentials } from "./railway";
|
||||||
import { RenderConnectionMethod } from "./render/render-connection-enums";
|
import { RenderConnectionMethod } from "./render/render-connection-enums";
|
||||||
import { getRenderConnectionListItem, validateRenderConnectionCredentials } from "./render/render-connection-fns";
|
import { getRenderConnectionListItem, validateRenderConnectionCredentials } from "./render/render-connection-fns";
|
||||||
|
import {
|
||||||
|
getSupabaseConnectionListItem,
|
||||||
|
SupabaseConnectionMethod,
|
||||||
|
validateSupabaseConnectionCredentials
|
||||||
|
} from "./supabase";
|
||||||
import {
|
import {
|
||||||
getTeamCityConnectionListItem,
|
getTeamCityConnectionListItem,
|
||||||
TeamCityConnectionMethod,
|
TeamCityConnectionMethod,
|
||||||
@@ -148,7 +153,8 @@ export const listAppConnectionOptions = () => {
|
|||||||
getZabbixConnectionListItem(),
|
getZabbixConnectionListItem(),
|
||||||
getRailwayConnectionListItem(),
|
getRailwayConnectionListItem(),
|
||||||
getBitbucketConnectionListItem(),
|
getBitbucketConnectionListItem(),
|
||||||
getChecklyConnectionListItem()
|
getChecklyConnectionListItem(),
|
||||||
|
getSupabaseConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +238,8 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
||||||
@@ -292,6 +299,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case RenderConnectionMethod.ApiKey:
|
case RenderConnectionMethod.ApiKey:
|
||||||
case ChecklyConnectionMethod.ApiKey:
|
case ChecklyConnectionMethod.ApiKey:
|
||||||
return "API Key";
|
return "API Key";
|
||||||
|
case SupabaseConnectionMethod.AccessToken:
|
||||||
|
return "Access Token";
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||||
@@ -355,7 +364,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported,
|
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
|
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
|
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Checkly]: platformManagedCredentialsNotSupported
|
[AppConnection.Checkly]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.Supabase]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enterpriseAppCheck = async (
|
export const enterpriseAppCheck = async (
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Zabbix]: "Zabbix",
|
[AppConnection.Zabbix]: "Zabbix",
|
||||||
[AppConnection.Railway]: "Railway",
|
[AppConnection.Railway]: "Railway",
|
||||||
[AppConnection.Bitbucket]: "Bitbucket",
|
[AppConnection.Bitbucket]: "Bitbucket",
|
||||||
[AppConnection.Checkly]: "Checkly"
|
[AppConnection.Checkly]: "Checkly",
|
||||||
|
[AppConnection.Supabase]: "Supabase"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
|
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
|
||||||
@@ -69,5 +70,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.Zabbix]: AppConnectionPlanType.Regular,
|
[AppConnection.Zabbix]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Railway]: AppConnectionPlanType.Regular,
|
[AppConnection.Railway]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
|
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Checkly]: AppConnectionPlanType.Regular
|
[AppConnection.Checkly]: AppConnectionPlanType.Regular,
|
||||||
|
[AppConnection.Supabase]: AppConnectionPlanType.Regular
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ import { ValidateRailwayConnectionCredentialsSchema } from "./railway";
|
|||||||
import { railwayConnectionService } from "./railway/railway-connection-service";
|
import { railwayConnectionService } from "./railway/railway-connection-service";
|
||||||
import { ValidateRenderConnectionCredentialsSchema } from "./render/render-connection-schema";
|
import { ValidateRenderConnectionCredentialsSchema } from "./render/render-connection-schema";
|
||||||
import { renderConnectionService } from "./render/render-connection-service";
|
import { renderConnectionService } from "./render/render-connection-service";
|
||||||
|
import { ValidateSupabaseConnectionCredentialsSchema } from "./supabase";
|
||||||
|
import { supabaseConnectionService } from "./supabase/supabase-connection-service";
|
||||||
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
||||||
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
||||||
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
|
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
|
||||||
@@ -131,7 +133,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema,
|
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema,
|
||||||
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
|
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
|
||||||
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
|
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
|
||||||
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema
|
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema,
|
||||||
|
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
@@ -545,6 +548,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
zabbix: zabbixConnectionService(connectAppConnectionById),
|
zabbix: zabbixConnectionService(connectAppConnectionById),
|
||||||
railway: railwayConnectionService(connectAppConnectionById),
|
railway: railwayConnectionService(connectAppConnectionById),
|
||||||
bitbucket: bitbucketConnectionService(connectAppConnectionById),
|
bitbucket: bitbucketConnectionService(connectAppConnectionById),
|
||||||
checkly: checklyConnectionService(connectAppConnectionById)
|
checkly: checklyConnectionService(connectAppConnectionById),
|
||||||
|
supabase: supabaseConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ import {
|
|||||||
TRenderConnectionInput,
|
TRenderConnectionInput,
|
||||||
TValidateRenderConnectionCredentialsSchema
|
TValidateRenderConnectionCredentialsSchema
|
||||||
} from "./render/render-connection-types";
|
} from "./render/render-connection-types";
|
||||||
|
import {
|
||||||
|
TSupabaseConnection,
|
||||||
|
TSupabaseConnectionConfig,
|
||||||
|
TSupabaseConnectionInput,
|
||||||
|
TValidateSupabaseConnectionCredentialsSchema
|
||||||
|
} from "./supabase";
|
||||||
import {
|
import {
|
||||||
TTeamCityConnection,
|
TTeamCityConnection,
|
||||||
TTeamCityConnectionConfig,
|
TTeamCityConnectionConfig,
|
||||||
@@ -224,6 +230,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TZabbixConnection
|
| TZabbixConnection
|
||||||
| TRailwayConnection
|
| TRailwayConnection
|
||||||
| TChecklyConnection
|
| TChecklyConnection
|
||||||
|
| TSupabaseConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
@@ -264,6 +271,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TZabbixConnectionInput
|
| TZabbixConnectionInput
|
||||||
| TRailwayConnectionInput
|
| TRailwayConnectionInput
|
||||||
| TChecklyConnectionInput
|
| TChecklyConnectionInput
|
||||||
|
| TSupabaseConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput =
|
export type TSqlConnectionInput =
|
||||||
@@ -311,7 +319,8 @@ export type TAppConnectionConfig =
|
|||||||
| TBitbucketConnectionConfig
|
| TBitbucketConnectionConfig
|
||||||
| TZabbixConnectionConfig
|
| TZabbixConnectionConfig
|
||||||
| TRailwayConnectionConfig
|
| TRailwayConnectionConfig
|
||||||
| TChecklyConnectionConfig;
|
| TChecklyConnectionConfig
|
||||||
|
| TSupabaseConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentialsSchema =
|
export type TValidateAppConnectionCredentialsSchema =
|
||||||
| TValidateAwsConnectionCredentialsSchema
|
| TValidateAwsConnectionCredentialsSchema
|
||||||
@@ -346,7 +355,8 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateBitbucketConnectionCredentialsSchema
|
| TValidateBitbucketConnectionCredentialsSchema
|
||||||
| TValidateZabbixConnectionCredentialsSchema
|
| TValidateZabbixConnectionCredentialsSchema
|
||||||
| TValidateRailwayConnectionCredentialsSchema
|
| TValidateRailwayConnectionCredentialsSchema
|
||||||
| TValidateChecklyConnectionCredentialsSchema;
|
| TValidateChecklyConnectionCredentialsSchema
|
||||||
|
| TValidateSupabaseConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./supabase-connection-constants";
|
||||||
|
export * from "./supabase-connection-fns";
|
||||||
|
export * from "./supabase-connection-schemas";
|
||||||
|
export * from "./supabase-connection-types";
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum SupabaseConnectionMethod {
|
||||||
|
AccessToken = "access-token"
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { SupabaseConnectionMethod } from "./supabase-connection-constants";
|
||||||
|
import { SupabasePublicAPI } from "./supabase-connection-public-client";
|
||||||
|
import { TSupabaseConnection, TSupabaseConnectionConfig } from "./supabase-connection-types";
|
||||||
|
|
||||||
|
export const getSupabaseConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "Supabase" as const,
|
||||||
|
app: AppConnection.Supabase as const,
|
||||||
|
methods: Object.values(SupabaseConnectionMethod)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateSupabaseConnectionCredentials = async (config: TSupabaseConnectionConfig) => {
|
||||||
|
const { credentials } = config;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await SupabasePublicAPI.healthcheck(config);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection - verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listProjects = async (appConnection: TSupabaseConnection) => {
|
||||||
|
try {
|
||||||
|
return await SupabasePublicAPI.getProjects(appConnection);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to list projects: ${error.message || "Unknown error"}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof BadRequestError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to list projects",
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import { AxiosInstance, AxiosRequestConfig, AxiosResponse, HttpStatusCode } from "axios";
|
||||||
|
|
||||||
|
import { createRequestClient } from "@app/lib/config/request";
|
||||||
|
import { delay } from "@app/lib/delay";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
|
|
||||||
|
import { SupabaseConnectionMethod } from "./supabase-connection-constants";
|
||||||
|
import { TSupabaseConnectionConfig, TSupabaseProject, TSupabaseSecret } from "./supabase-connection-types";
|
||||||
|
|
||||||
|
export const getSupabaseInstanceUrl = async (config: TSupabaseConnectionConfig) => {
|
||||||
|
const instanceUrl = config.credentials.instanceUrl
|
||||||
|
? removeTrailingSlash(config.credentials.instanceUrl)
|
||||||
|
: "https://api.supabase.com";
|
||||||
|
|
||||||
|
await blockLocalAndPrivateIpAddresses(instanceUrl);
|
||||||
|
|
||||||
|
return instanceUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getSupabaseAuthHeaders(connection: TSupabaseConnectionConfig): Record<string, string> {
|
||||||
|
switch (connection.method) {
|
||||||
|
case SupabaseConnectionMethod.AccessToken:
|
||||||
|
return {
|
||||||
|
Authorization: `Bearer ${connection.credentials.accessKey}`
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported Supabase connection method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSupabaseRatelimiter(response: AxiosResponse): {
|
||||||
|
maxAttempts: number;
|
||||||
|
isRatelimited: boolean;
|
||||||
|
wait: () => Promise<void>;
|
||||||
|
} {
|
||||||
|
const wait = () => {
|
||||||
|
return delay(60 * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isRatelimited: response.status === HttpStatusCode.TooManyRequests,
|
||||||
|
wait,
|
||||||
|
maxAttempts: 3
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SupabasePublicClient {
|
||||||
|
private client: AxiosInstance;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client = createRequestClient({
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async send<T>(
|
||||||
|
connection: TSupabaseConnectionConfig,
|
||||||
|
config: AxiosRequestConfig,
|
||||||
|
retryAttempt = 0
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const response = await this.client.request<T>({
|
||||||
|
...config,
|
||||||
|
baseURL: await getSupabaseInstanceUrl(connection),
|
||||||
|
validateStatus: (status) => (status >= 200 && status < 300) || status === HttpStatusCode.TooManyRequests,
|
||||||
|
headers: getSupabaseAuthHeaders(connection)
|
||||||
|
});
|
||||||
|
|
||||||
|
const limiter = getSupabaseRatelimiter(response);
|
||||||
|
|
||||||
|
if (limiter.isRatelimited && retryAttempt <= limiter.maxAttempts) {
|
||||||
|
await limiter.wait();
|
||||||
|
return this.send(connection, config, retryAttempt + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async healthcheck(connection: TSupabaseConnectionConfig) {
|
||||||
|
switch (connection.method) {
|
||||||
|
case SupabaseConnectionMethod.AccessToken:
|
||||||
|
return void (await this.getProjects(connection));
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported Supabase connection method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariables(connection: TSupabaseConnectionConfig, projectRef: string) {
|
||||||
|
const res = await this.send<TSupabaseSecret[]>(connection, {
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/projects/${projectRef}/secrets`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supabase does not support updating variables directly
|
||||||
|
// Instead, just call create again with the same key and it will overwrite the existing variable
|
||||||
|
async createVariables(connection: TSupabaseConnectionConfig, projectRef: string, ...variables: TSupabaseSecret[]) {
|
||||||
|
const res = await this.send<TSupabaseSecret>(connection, {
|
||||||
|
method: "POST",
|
||||||
|
url: `/v1/projects/${projectRef}/secrets`,
|
||||||
|
data: variables
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteVariables(connection: TSupabaseConnectionConfig, projectRef: string, ...variables: string[]) {
|
||||||
|
const res = await this.send(connection, {
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/v1/projects/${projectRef}/secrets`,
|
||||||
|
data: variables
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProjects(connection: TSupabaseConnectionConfig) {
|
||||||
|
const res = await this.send<TSupabaseProject[]>(connection, {
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/projects`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SupabasePublicAPI = new SupabasePublicClient();
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
|
||||||
|
import { SupabaseConnectionMethod } from "./supabase-connection-constants";
|
||||||
|
|
||||||
|
export const SupabaseConnectionMethodSchema = z
|
||||||
|
.nativeEnum(SupabaseConnectionMethod)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.Supabase).method);
|
||||||
|
|
||||||
|
export const SupabaseConnectionAccessTokenCredentialsSchema = z.object({
|
||||||
|
accessKey: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Access Key required")
|
||||||
|
.max(255)
|
||||||
|
.describe(AppConnections.CREDENTIALS.SUPABASE.accessKey),
|
||||||
|
instanceUrl: z.string().trim().url().max(255).describe(AppConnections.CREDENTIALS.SUPABASE.instanceUrl).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
const BaseSupabaseConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.Supabase)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SupabaseConnectionSchema = BaseSupabaseConnectionSchema.extend({
|
||||||
|
method: SupabaseConnectionMethodSchema,
|
||||||
|
credentials: SupabaseConnectionAccessTokenCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedSupabaseConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseSupabaseConnectionSchema.extend({
|
||||||
|
method: SupabaseConnectionMethodSchema,
|
||||||
|
credentials: SupabaseConnectionAccessTokenCredentialsSchema.pick({
|
||||||
|
instanceUrl: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateSupabaseConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: SupabaseConnectionMethodSchema,
|
||||||
|
credentials: SupabaseConnectionAccessTokenCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.Supabase).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateSupabaseConnectionSchema = ValidateSupabaseConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.Supabase)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateSupabaseConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: SupabaseConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.Supabase).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Supabase));
|
||||||
|
|
||||||
|
export const SupabaseConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Supabase"),
|
||||||
|
app: z.literal(AppConnection.Supabase),
|
||||||
|
methods: z.nativeEnum(SupabaseConnectionMethod).array()
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { listProjects as getSupabaseProjects } from "./supabase-connection-fns";
|
||||||
|
import { TSupabaseConnection } from "./supabase-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TSupabaseConnection>;
|
||||||
|
|
||||||
|
export const supabaseConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
|
const listProjects = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.Supabase, connectionId, actor);
|
||||||
|
try {
|
||||||
|
const projects = await getSupabaseProjects(appConnection);
|
||||||
|
|
||||||
|
return projects ?? [];
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to establish connection with Supabase");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listProjects
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateSupabaseConnectionSchema,
|
||||||
|
SupabaseConnectionSchema,
|
||||||
|
ValidateSupabaseConnectionCredentialsSchema
|
||||||
|
} from "./supabase-connection-schemas";
|
||||||
|
|
||||||
|
export type TSupabaseConnection = z.infer<typeof SupabaseConnectionSchema>;
|
||||||
|
|
||||||
|
export type TSupabaseConnectionInput = z.infer<typeof CreateSupabaseConnectionSchema> & {
|
||||||
|
app: AppConnection.Supabase;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateSupabaseConnectionCredentialsSchema = typeof ValidateSupabaseConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TSupabaseConnectionConfig = DiscriminativePick<TSupabaseConnection, "method" | "app" | "credentials"> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSupabaseProject = {
|
||||||
|
id: string;
|
||||||
|
organization_id: string;
|
||||||
|
name: string;
|
||||||
|
region: string;
|
||||||
|
created_at: Date;
|
||||||
|
status: string;
|
||||||
|
database: TSupabaseDatabase;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TSupabaseDatabase = {
|
||||||
|
host: string;
|
||||||
|
version: string;
|
||||||
|
postgres_engine: string;
|
||||||
|
release_channel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSupabaseSecret = {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
@@ -22,7 +22,7 @@ export enum SecretSync {
|
|||||||
GitLab = "gitlab",
|
GitLab = "gitlab",
|
||||||
CloudflarePages = "cloudflare-pages",
|
CloudflarePages = "cloudflare-pages",
|
||||||
CloudflareWorkers = "cloudflare-workers",
|
CloudflareWorkers = "cloudflare-workers",
|
||||||
|
Supabase = "supabase",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly"
|
Checkly = "checkly"
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants";
|
|||||||
import { RailwaySyncFns } from "./railway/railway-sync-fns";
|
import { RailwaySyncFns } from "./railway/railway-sync-fns";
|
||||||
import { RENDER_SYNC_LIST_OPTION, RenderSyncFns } from "./render";
|
import { RENDER_SYNC_LIST_OPTION, RenderSyncFns } from "./render";
|
||||||
import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps";
|
import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps";
|
||||||
|
import { SUPABASE_SYNC_LIST_OPTION, SupabaseSyncFns } from "./supabase";
|
||||||
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
|
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
|
||||||
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
|
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
|
||||||
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
|
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
|
||||||
@@ -76,7 +77,7 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
|||||||
[SecretSync.GitLab]: GITLAB_SYNC_LIST_OPTION,
|
[SecretSync.GitLab]: GITLAB_SYNC_LIST_OPTION,
|
||||||
[SecretSync.CloudflarePages]: CLOUDFLARE_PAGES_SYNC_LIST_OPTION,
|
[SecretSync.CloudflarePages]: CLOUDFLARE_PAGES_SYNC_LIST_OPTION,
|
||||||
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_SYNC_LIST_OPTION,
|
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.Supabase]: SUPABASE_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION
|
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION
|
||||||
@@ -255,6 +256,8 @@ export const SecretSyncFns = {
|
|||||||
return RailwaySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return RailwaySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@@ -359,6 +362,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
secretMap = await ChecklySyncFns.getSecrets(secretSync);
|
secretMap = await ChecklySyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
secretMap = await SupabaseSyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@@ -444,6 +450,8 @@ export const SecretSyncFns = {
|
|||||||
return RailwaySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return RailwaySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.GitLab]: "GitLab",
|
[SecretSync.GitLab]: "GitLab",
|
||||||
[SecretSync.CloudflarePages]: "Cloudflare Pages",
|
[SecretSync.CloudflarePages]: "Cloudflare Pages",
|
||||||
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
|
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
|
||||||
|
[SecretSync.Supabase]: "Supabase",
|
||||||
[SecretSync.Zabbix]: "Zabbix",
|
[SecretSync.Zabbix]: "Zabbix",
|
||||||
[SecretSync.Railway]: "Railway",
|
[SecretSync.Railway]: "Railway",
|
||||||
[SecretSync.Checkly]: "Checkly"
|
[SecretSync.Checkly]: "Checkly"
|
||||||
@@ -55,7 +55,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.GitLab]: AppConnection.GitLab,
|
[SecretSync.GitLab]: AppConnection.GitLab,
|
||||||
[SecretSync.CloudflarePages]: AppConnection.Cloudflare,
|
[SecretSync.CloudflarePages]: AppConnection.Cloudflare,
|
||||||
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
||||||
|
[SecretSync.Supabase]: AppConnection.Supabase,
|
||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway,
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
[SecretSync.Checkly]: AppConnection.Checkly
|
[SecretSync.Checkly]: AppConnection.Checkly
|
||||||
@@ -85,7 +85,7 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
|||||||
[SecretSync.GitLab]: SecretSyncPlanType.Regular,
|
[SecretSync.GitLab]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.CloudflarePages]: SecretSyncPlanType.Regular,
|
[SecretSync.CloudflarePages]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
|
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Supabase]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Checkly]: SecretSyncPlanType.Regular
|
[SecretSync.Checkly]: SecretSyncPlanType.Regular
|
||||||
|
|||||||
@@ -118,6 +118,12 @@ import {
|
|||||||
TRenderSyncListItem,
|
TRenderSyncListItem,
|
||||||
TRenderSyncWithCredentials
|
TRenderSyncWithCredentials
|
||||||
} from "./render/render-sync-types";
|
} from "./render/render-sync-types";
|
||||||
|
import {
|
||||||
|
TSupabaseSync,
|
||||||
|
TSupabaseSyncInput,
|
||||||
|
TSupabaseSyncListItem,
|
||||||
|
TSupabaseSyncWithCredentials
|
||||||
|
} from "./supabase/supabase-sync-types";
|
||||||
import {
|
import {
|
||||||
TTeamCitySync,
|
TTeamCitySync,
|
||||||
TTeamCitySyncInput,
|
TTeamCitySyncInput,
|
||||||
@@ -159,7 +165,8 @@ export type TSecretSync =
|
|||||||
| TCloudflareWorkersSync
|
| TCloudflareWorkersSync
|
||||||
| TZabbixSync
|
| TZabbixSync
|
||||||
| TRailwaySync
|
| TRailwaySync
|
||||||
| TChecklySync;
|
| TChecklySync
|
||||||
|
| TSupabaseSync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
| TAwsParameterStoreSyncWithCredentials
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
@@ -187,7 +194,8 @@ export type TSecretSyncWithCredentials =
|
|||||||
| TCloudflareWorkersSyncWithCredentials
|
| TCloudflareWorkersSyncWithCredentials
|
||||||
| TZabbixSyncWithCredentials
|
| TZabbixSyncWithCredentials
|
||||||
| TRailwaySyncWithCredentials
|
| TRailwaySyncWithCredentials
|
||||||
| TChecklySyncWithCredentials;
|
| TChecklySyncWithCredentials
|
||||||
|
| TSupabaseSyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
| TAwsParameterStoreSyncInput
|
| TAwsParameterStoreSyncInput
|
||||||
@@ -215,7 +223,8 @@ export type TSecretSyncInput =
|
|||||||
| TCloudflareWorkersSyncInput
|
| TCloudflareWorkersSyncInput
|
||||||
| TZabbixSyncInput
|
| TZabbixSyncInput
|
||||||
| TRailwaySyncInput
|
| TRailwaySyncInput
|
||||||
| TChecklySyncInput;
|
| TChecklySyncInput
|
||||||
|
| TSupabaseSyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
| TAwsParameterStoreSyncListItem
|
| TAwsParameterStoreSyncListItem
|
||||||
@@ -243,7 +252,8 @@ export type TSecretSyncListItem =
|
|||||||
| TCloudflareWorkersSyncListItem
|
| TCloudflareWorkersSyncListItem
|
||||||
| TZabbixSyncListItem
|
| TZabbixSyncListItem
|
||||||
| TRailwaySyncListItem
|
| TRailwaySyncListItem
|
||||||
| TChecklySyncListItem;
|
| TChecklySyncListItem
|
||||||
|
| TSupabaseSyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./supabase-sync-constants";
|
||||||
|
export * from "./supabase-sync-fns";
|
||||||
|
export * from "./supabase-sync-schemas";
|
||||||
|
export * from "./supabase-sync-types";
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
export const SUPABASE_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Supabase",
|
||||||
|
destination: SecretSync.Supabase,
|
||||||
|
connection: AppConnection.Supabase,
|
||||||
|
canImportSecrets: false
|
||||||
|
};
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/* eslint-disable no-continue */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
|
||||||
|
import { chunkArray } from "@app/lib/fn";
|
||||||
|
import { TSupabaseSecret } from "@app/services/app-connection/supabase";
|
||||||
|
import { SupabasePublicAPI } from "@app/services/app-connection/supabase/supabase-connection-public-client";
|
||||||
|
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
|
|
||||||
|
import { SecretSyncError } from "../secret-sync-errors";
|
||||||
|
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||||
|
import { TSecretMap } from "../secret-sync-types";
|
||||||
|
import { TSupabaseSyncWithCredentials } from "./supabase-sync-types";
|
||||||
|
|
||||||
|
const SUPABASE_INTERNAL_SECRETS = ["SUPABASE_URL", "SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY", "SUPABASE_DB_URL"];
|
||||||
|
|
||||||
|
export const SupabaseSyncFns = {
|
||||||
|
async getSecrets(secretSync: TSupabaseSyncWithCredentials) {
|
||||||
|
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async syncSecrets(secretSync: TSupabaseSyncWithCredentials, secretMap: TSecretMap) {
|
||||||
|
const {
|
||||||
|
environment,
|
||||||
|
syncOptions: { disableSecretDeletion, keySchema }
|
||||||
|
} = secretSync;
|
||||||
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
|
const variables = await SupabasePublicAPI.getVariables(secretSync.connection, config.projectId);
|
||||||
|
|
||||||
|
const supabaseSecrets = new Map(variables!.map((variable) => [variable.name, variable]));
|
||||||
|
|
||||||
|
const toCreate: TSupabaseSecret[] = [];
|
||||||
|
|
||||||
|
for (const key of Object.keys(secretMap)) {
|
||||||
|
const variable: TSupabaseSecret = { name: key, value: secretMap[key].value ?? "" };
|
||||||
|
toCreate.push(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const batch of chunkArray(toCreate, 100)) {
|
||||||
|
try {
|
||||||
|
await SupabasePublicAPI.createVariables(secretSync.connection, config.projectId, ...batch);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: batch[0].name // Use the first key in the batch for error reporting
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableSecretDeletion) return;
|
||||||
|
|
||||||
|
const toDelete: string[] = [];
|
||||||
|
|
||||||
|
for (const key of supabaseSecrets.keys()) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!matchesSchema(key, environment?.slug || "", keySchema) || SUPABASE_INTERNAL_SECRETS.includes(key)) continue;
|
||||||
|
|
||||||
|
if (!secretMap[key]) {
|
||||||
|
toDelete.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const batch of chunkArray(toDelete, 100)) {
|
||||||
|
try {
|
||||||
|
await SupabasePublicAPI.deleteVariables(secretSync.connection, config.projectId, ...batch);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: batch[0] // Use the first key in the batch for error reporting
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeSecrets(secretSync: TSupabaseSyncWithCredentials, secretMap: TSecretMap) {
|
||||||
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
|
const variables = await SupabasePublicAPI.getVariables(secretSync.connection, config.projectId);
|
||||||
|
|
||||||
|
const supabaseSecrets = new Map(variables!.map((variable) => [variable.name, variable]));
|
||||||
|
|
||||||
|
const toDelete: string[] = [];
|
||||||
|
|
||||||
|
for (const key of supabaseSecrets.keys()) {
|
||||||
|
if (SUPABASE_INTERNAL_SECRETS.includes(key) || !(key in secretMap)) continue;
|
||||||
|
|
||||||
|
toDelete.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const batch of chunkArray(toDelete, 100)) {
|
||||||
|
try {
|
||||||
|
await SupabasePublicAPI.deleteVariables(secretSync.connection, config.projectId, ...batch);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: batch[0] // Use the first key in the batch for error reporting
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
BaseSecretSyncSchema,
|
||||||
|
GenericCreateSecretSyncFieldsSchema,
|
||||||
|
GenericUpdateSecretSyncFieldsSchema
|
||||||
|
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||||
|
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
const SupabaseSyncDestinationConfigSchema = z.object({
|
||||||
|
projectId: z.string().max(255).min(1, "Project ID is required"),
|
||||||
|
projectName: z.string().max(255).min(1, "Project Name is required")
|
||||||
|
});
|
||||||
|
|
||||||
|
const SupabaseSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||||
|
|
||||||
|
export const SupabaseSyncSchema = BaseSecretSyncSchema(SecretSync.Supabase, SupabaseSyncOptionsConfig).extend({
|
||||||
|
destination: z.literal(SecretSync.Supabase),
|
||||||
|
destinationConfig: SupabaseSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateSupabaseSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.Supabase,
|
||||||
|
SupabaseSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: SupabaseSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateSupabaseSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.Supabase,
|
||||||
|
SupabaseSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: SupabaseSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SupabaseSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Supabase"),
|
||||||
|
connection: z.literal(AppConnection.Supabase),
|
||||||
|
destination: z.literal(SecretSync.Supabase),
|
||||||
|
canImportSecrets: z.literal(false)
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { TSupabaseConnection } from "@app/services/app-connection/supabase";
|
||||||
|
|
||||||
|
import { CreateSupabaseSyncSchema, SupabaseSyncListItemSchema, SupabaseSyncSchema } from "./supabase-sync-schemas";
|
||||||
|
|
||||||
|
export type TSupabaseSyncListItem = z.infer<typeof SupabaseSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TSupabaseSync = z.infer<typeof SupabaseSyncSchema>;
|
||||||
|
|
||||||
|
export type TSupabaseSyncInput = z.infer<typeof CreateSupabaseSyncSchema>;
|
||||||
|
|
||||||
|
export type TSupabaseSyncWithCredentials = TSupabaseSync & {
|
||||||
|
connection: TSupabaseConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSupabaseVariablesGraphResponse = {
|
||||||
|
data: {
|
||||||
|
variables: Record<string, string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/supabase/available"
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/supabase"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Supabase Connections](/integrations/app-connections/supabase) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/supabase/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/supabase/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/supabase/connection-name/{connectionName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/supabase"
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/supabase/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Supabase Connections](/integrations/app-connections/supabase) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/supabase"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/supabase/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/supabase/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/supabase/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/supabase"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/supabase/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/supabase/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/supabase/{syncId}"
|
||||||
|
---
|
||||||
@@ -78,10 +78,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Infisical SSH",
|
"group": "Infisical SSH",
|
||||||
"pages": [
|
"pages": ["documentation/platform/ssh/overview", "documentation/platform/ssh/host-groups"]
|
||||||
"documentation/platform/ssh/overview",
|
|
||||||
"documentation/platform/ssh/host-groups"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Key Management (KMS)",
|
"group": "Key Management (KMS)",
|
||||||
@@ -378,10 +375,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Architecture",
|
"group": "Architecture",
|
||||||
"pages": [
|
"pages": ["internals/architecture/components", "internals/architecture/cloud"]
|
||||||
"internals/architecture/components",
|
|
||||||
"internals/architecture/cloud"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"internals/security",
|
"internals/security",
|
||||||
"internals/service-tokens"
|
"internals/service-tokens"
|
||||||
@@ -491,6 +485,7 @@
|
|||||||
"integrations/app-connections/postgres",
|
"integrations/app-connections/postgres",
|
||||||
"integrations/app-connections/railway",
|
"integrations/app-connections/railway",
|
||||||
"integrations/app-connections/render",
|
"integrations/app-connections/render",
|
||||||
|
"integrations/app-connections/supabase",
|
||||||
"integrations/app-connections/teamcity",
|
"integrations/app-connections/teamcity",
|
||||||
"integrations/app-connections/terraform-cloud",
|
"integrations/app-connections/terraform-cloud",
|
||||||
"integrations/app-connections/vercel",
|
"integrations/app-connections/vercel",
|
||||||
@@ -528,6 +523,7 @@
|
|||||||
"integrations/secret-syncs/oci-vault",
|
"integrations/secret-syncs/oci-vault",
|
||||||
"integrations/secret-syncs/railway",
|
"integrations/secret-syncs/railway",
|
||||||
"integrations/secret-syncs/render",
|
"integrations/secret-syncs/render",
|
||||||
|
"integrations/secret-syncs/supabase",
|
||||||
"integrations/secret-syncs/teamcity",
|
"integrations/secret-syncs/teamcity",
|
||||||
"integrations/secret-syncs/terraform-cloud",
|
"integrations/secret-syncs/terraform-cloud",
|
||||||
"integrations/secret-syncs/vercel",
|
"integrations/secret-syncs/vercel",
|
||||||
@@ -555,10 +551,7 @@
|
|||||||
"integrations/cloud/gcp-secret-manager",
|
"integrations/cloud/gcp-secret-manager",
|
||||||
{
|
{
|
||||||
"group": "Cloudflare",
|
"group": "Cloudflare",
|
||||||
"pages": [
|
"pages": ["integrations/cloud/cloudflare-pages", "integrations/cloud/cloudflare-workers"]
|
||||||
"integrations/cloud/cloudflare-pages",
|
|
||||||
"integrations/cloud/cloudflare-workers"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"integrations/cloud/terraform-cloud",
|
"integrations/cloud/terraform-cloud",
|
||||||
"integrations/cloud/databricks",
|
"integrations/cloud/databricks",
|
||||||
@@ -670,11 +663,7 @@
|
|||||||
"cli/commands/reset",
|
"cli/commands/reset",
|
||||||
{
|
{
|
||||||
"group": "infisical scan",
|
"group": "infisical scan",
|
||||||
"pages": [
|
"pages": ["cli/commands/scan", "cli/commands/scan-git-changes", "cli/commands/scan-install"]
|
||||||
"cli/commands/scan",
|
|
||||||
"cli/commands/scan-git-changes",
|
|
||||||
"cli/commands/scan-install"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -998,9 +987,7 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"group": "Kubernetes",
|
"group": "Kubernetes",
|
||||||
"pages": [
|
"pages": ["api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"]
|
||||||
"api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"api-reference/endpoints/dynamic-secrets/create",
|
"api-reference/endpoints/dynamic-secrets/create",
|
||||||
"api-reference/endpoints/dynamic-secrets/update",
|
"api-reference/endpoints/dynamic-secrets/update",
|
||||||
@@ -1557,6 +1544,18 @@
|
|||||||
"api-reference/endpoints/app-connections/render/delete"
|
"api-reference/endpoints/app-connections/render/delete"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Supabase",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/app-connections/supabase/list",
|
||||||
|
"api-reference/endpoints/app-connections/supabase/available",
|
||||||
|
"api-reference/endpoints/app-connections/supabase/get-by-id",
|
||||||
|
"api-reference/endpoints/app-connections/supabase/get-by-name",
|
||||||
|
"api-reference/endpoints/app-connections/supabase/create",
|
||||||
|
"api-reference/endpoints/app-connections/supabase/update",
|
||||||
|
"api-reference/endpoints/app-connections/supabase/delete"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "TeamCity",
|
"group": "TeamCity",
|
||||||
"pages": [
|
"pages": [
|
||||||
@@ -1908,6 +1907,19 @@
|
|||||||
"api-reference/endpoints/secret-syncs/render/remove-secrets"
|
"api-reference/endpoints/secret-syncs/render/remove-secrets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Supabase",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/list",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/create",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/update",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/delete",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/sync-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/supabase/remove-secrets"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "TeamCity",
|
"group": "TeamCity",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 494 KiB |
|
After Width: | Height: | Size: 684 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 413 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 558 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 635 KiB |
|
After Width: | Height: | Size: 639 KiB |
|
After Width: | Height: | Size: 700 KiB |
|
After Width: | Height: | Size: 658 KiB |
|
After Width: | Height: | Size: 620 KiB |
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
title: "Supabase Connection"
|
||||||
|
description: "Learn how to configure a Supabase Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical supports the use of [Personal Access Tokens](https://supabase.com/dashboard/account/tokens) to connect with Supabase.
|
||||||
|
|
||||||
|
## Create a Supabase Personal Access Token
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Click the profile image in the top-right corner and select 'Account Preferences'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="In the sidebar, select 'Access Tokens'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="In the access tokens page, click on 'Generate New Token'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Enter a token name and click on 'Generate Token'">
|
||||||
|
Provide a descriptive name for the token.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Copy the generated token and save it">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Create a Supabase Connection in Infisical
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to App Connections">
|
||||||
|
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select Supabase Connection">
|
||||||
|
Click **+ Add Connection** and choose **Supabase Connection** from the list of integrations.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Fill out the Supabase Connection form">
|
||||||
|
Complete the form by providing:
|
||||||
|
- A descriptive name for the connection
|
||||||
|
- An optional description
|
||||||
|
- Supabase instance URL (e.g., `https://your-domain.com` or `https://api.supabase.com`)
|
||||||
|
- The Access Token value from the previous step
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Connection created">
|
||||||
|
After submitting the form, your **Supabase Connection** will be successfully created and ready to use with your Infisical projects.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="API">
|
||||||
|
To create a Supabase Connection via API, send a request to the [Create Supabase Connection](/api-reference/endpoints/app-connections/supabase/create) endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/app-connections/supabase \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-supabase-connection",
|
||||||
|
"method": "access-token",
|
||||||
|
"credentials": {
|
||||||
|
"accessToken": "[Access Token]",
|
||||||
|
"instanceUrl": "https://api.supabase.com"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"appConnection": {
|
||||||
|
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||||
|
"name": "my-supabase-connection",
|
||||||
|
"description": null,
|
||||||
|
"version": 1,
|
||||||
|
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||||
|
"createdAt": "2025-04-23T19:46:34.831Z",
|
||||||
|
"updatedAt": "2025-04-23T19:46:34.831Z",
|
||||||
|
"isPlatformManagedCredentials": false,
|
||||||
|
"credentialsHash": "7c2d371dec195f82a6a0d5b41c970a229cfcaf88e894a5b6395e2dbd0280661f",
|
||||||
|
"app": "supabase",
|
||||||
|
"method": "access-token",
|
||||||
|
"credentials": {
|
||||||
|
"instanceUrl": "https://api.supabase.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
title: "Supabase Sync"
|
||||||
|
description: "Learn how to configure a Supabase Sync for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Create a [Supabase Connection](/integrations/app-connections/supabase)
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Add Sync">
|
||||||
|
Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select 'Supabase'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure source">
|
||||||
|
Configure the **Source** from where secrets should be retrieved, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Environment**: The project environment to retrieve secrets from.
|
||||||
|
- **Secret Path**: The folder path to retrieve secrets from.
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
|
||||||
|
</Tip>
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure destination">
|
||||||
|
Configure the **Destination** to where secrets should be deployed, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Supabase Connection**: The Supabase Connection to authenticate with.
|
||||||
|
- **Project**: The Supabase project to sync secrets to.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Sync Options">
|
||||||
|
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
|
||||||
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
|
<Note>
|
||||||
|
Supabase does not support importing secrets.
|
||||||
|
</Note>
|
||||||
|
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment.
|
||||||
|
<Note>
|
||||||
|
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
|
||||||
|
</Note>
|
||||||
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure details">
|
||||||
|
Configure the **Details** of your Supabase Sync, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Name**: The name of your sync. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description for your sync.
|
||||||
|
</Step>
|
||||||
|
<Step title="Review configuration">
|
||||||
|
Review your Supabase Sync configuration, then click **Create Sync**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Sync created">
|
||||||
|
If enabled, your Supabase Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create a **Supabase Sync**, make an API request to the [Create Supabase Sync](/api-reference/endpoints/secret-syncs/supabase/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/secret-syncs/supabase \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-supabase-sync",
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"description": "an example sync",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"environment": "dev",
|
||||||
|
"secretPath": "/my-secrets",
|
||||||
|
"isEnabled": true,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination",
|
||||||
|
"autoSyncEnabled": true,
|
||||||
|
"disableSecretDeletion": false
|
||||||
|
},
|
||||||
|
"destinationConfig": {
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"projectName": "Example Project"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretSync": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-supabase-sync",
|
||||||
|
"description": "an example sync",
|
||||||
|
"isEnabled": true,
|
||||||
|
"version": 1,
|
||||||
|
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"createdAt": "2023-11-07T05:31:56Z",
|
||||||
|
"updatedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"syncStatus": "succeeded",
|
||||||
|
"lastSyncJobId": "123",
|
||||||
|
"lastSyncMessage": null,
|
||||||
|
"lastSyncedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"importStatus": null,
|
||||||
|
"lastImportJobId": null,
|
||||||
|
"lastImportMessage": null,
|
||||||
|
"lastImportedAt": null,
|
||||||
|
"removeStatus": null,
|
||||||
|
"lastRemoveJobId": null,
|
||||||
|
"lastRemoveMessage": null,
|
||||||
|
"lastRemovedAt": null,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination",
|
||||||
|
"autoSyncEnabled": true,
|
||||||
|
"disableSecretDeletion": false
|
||||||
|
},
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connection": {
|
||||||
|
"app": "supabase",
|
||||||
|
"name": "my-supabase-connection",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"slug": "dev",
|
||||||
|
"name": "Development",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"path": "/my-secrets"
|
||||||
|
},
|
||||||
|
"destination": "supabase",
|
||||||
|
"destinationConfig": {
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"projectName": "Example Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
@@ -24,6 +24,7 @@ import { HumanitecSyncFields } from "./HumanitecSyncFields";
|
|||||||
import { OCIVaultSyncFields } from "./OCIVaultSyncFields";
|
import { OCIVaultSyncFields } from "./OCIVaultSyncFields";
|
||||||
import { RailwaySyncFields } from "./RailwaySyncFields";
|
import { RailwaySyncFields } from "./RailwaySyncFields";
|
||||||
import { RenderSyncFields } from "./RenderSyncFields";
|
import { RenderSyncFields } from "./RenderSyncFields";
|
||||||
|
import { SupabaseSyncFields } from "./SupabaseSyncFields";
|
||||||
import { TeamCitySyncFields } from "./TeamCitySyncFields";
|
import { TeamCitySyncFields } from "./TeamCitySyncFields";
|
||||||
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
|
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
|
||||||
import { VercelSyncFields } from "./VercelSyncFields";
|
import { VercelSyncFields } from "./VercelSyncFields";
|
||||||
@@ -88,6 +89,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
return <RailwaySyncFields />;
|
return <RailwaySyncFields />;
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
return <ChecklySyncFields />;
|
return <ChecklySyncFields />;
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
return <SupabaseSyncFields />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
|
||||||
|
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||||
|
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
TSupabaseProject,
|
||||||
|
useSupabaseConnectionListProjects
|
||||||
|
} from "@app/hooks/api/appConnections/supabase";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
|
export const SupabaseSyncFields = () => {
|
||||||
|
const { control, setValue } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.Supabase }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
|
||||||
|
const { data: projects = [], isPending: isProjectsLoading } = useSupabaseConnectionListProjects(
|
||||||
|
connectionId,
|
||||||
|
{
|
||||||
|
enabled: Boolean(connectionId)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField
|
||||||
|
onChange={() => {
|
||||||
|
setValue("destinationConfig.projectName", "");
|
||||||
|
setValue("destinationConfig.projectId", "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.projectId"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Select a project"
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isProjectsLoading && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={projects.find((p) => p.id === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const v = option as SingleValue<TSupabaseProject>;
|
||||||
|
onChange(v?.id ?? null);
|
||||||
|
setValue("destinationConfig.projectName", v?.name ?? "");
|
||||||
|
}}
|
||||||
|
options={projects}
|
||||||
|
placeholder="Select project..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -62,6 +62,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.Zabbix:
|
case SecretSync.Zabbix:
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
|
case SecretSync.Supabase:
|
||||||
AdditionalSyncOptionsFieldsComponent = null;
|
AdditionalSyncOptionsFieldsComponent = null;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
|
|||||||
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
|
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
|
||||||
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
|
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
|
||||||
import { RenderSyncReviewFields } from "./RenderSyncReviewFields";
|
import { RenderSyncReviewFields } from "./RenderSyncReviewFields";
|
||||||
|
import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields";
|
||||||
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
||||||
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
||||||
import { VercelSyncReviewFields } from "./VercelSyncReviewFields";
|
import { VercelSyncReviewFields } from "./VercelSyncReviewFields";
|
||||||
@@ -140,6 +141,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
DestinationFieldsComponent = <ChecklySyncReviewFields />;
|
DestinationFieldsComponent = <ChecklySyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { GenericFieldLabel } from "@app/components/v2";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const SupabaseSyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Supabase }>();
|
||||||
|
const projectName = watch("destinationConfig.projectName");
|
||||||
|
|
||||||
|
return <GenericFieldLabel label="Project">{projectName}</GenericFieldLabel>;
|
||||||
|
};
|
||||||
@@ -21,6 +21,7 @@ import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-sch
|
|||||||
import { OCIVaultSyncDestinationSchema } from "./oci-vault-sync-destination-schema";
|
import { OCIVaultSyncDestinationSchema } from "./oci-vault-sync-destination-schema";
|
||||||
import { RailwaySyncDestinationSchema } from "./railway-sync-destination-schema";
|
import { RailwaySyncDestinationSchema } from "./railway-sync-destination-schema";
|
||||||
import { RenderSyncDestinationSchema } from "./render-sync-destination-schema";
|
import { RenderSyncDestinationSchema } from "./render-sync-destination-schema";
|
||||||
|
import { SupabaseSyncDestinationSchema } from "./supabase-sync-destination-schema";
|
||||||
import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema";
|
import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema";
|
||||||
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
|
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
|
||||||
import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema";
|
import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema";
|
||||||
@@ -51,7 +52,7 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
|||||||
GitlabSyncDestinationSchema,
|
GitlabSyncDestinationSchema,
|
||||||
CloudflarePagesSyncDestinationSchema,
|
CloudflarePagesSyncDestinationSchema,
|
||||||
CloudflareWorkersSyncDestinationSchema,
|
CloudflareWorkersSyncDestinationSchema,
|
||||||
|
SupabaseSyncDestinationSchema,
|
||||||
ZabbixSyncDestinationSchema,
|
ZabbixSyncDestinationSchema,
|
||||||
RailwaySyncDestinationSchema,
|
RailwaySyncDestinationSchema,
|
||||||
ChecklySyncDestinationSchema
|
ChecklySyncDestinationSchema
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const SupabaseSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||||
|
z.object({
|
||||||
|
destination: z.literal(SecretSync.Supabase),
|
||||||
|
destinationConfig: z.object({
|
||||||
|
projectId: z.string().max(255).min(1, "Project ID is required"),
|
||||||
|
projectName: z.string().max(255).min(1, "Project Name is required")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -46,6 +46,7 @@ import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/hero
|
|||||||
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
||||||
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
||||||
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
|
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
|
||||||
|
import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection";
|
||||||
|
|
||||||
export const APP_CONNECTION_MAP: Record<
|
export const APP_CONNECTION_MAP: Record<
|
||||||
AppConnection,
|
AppConnection,
|
||||||
@@ -96,7 +97,8 @@ export const APP_CONNECTION_MAP: Record<
|
|||||||
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
|
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
|
||||||
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
|
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
|
||||||
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
|
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
|
||||||
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" }
|
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" },
|
||||||
|
[AppConnection.Supabase]: { name: "Supabase", image: "Supabase.png" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
||||||
@@ -151,6 +153,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
|||||||
case HerokuConnectionMethod.AuthToken:
|
case HerokuConnectionMethod.AuthToken:
|
||||||
return { name: "Auth Token", icon: faKey };
|
return { name: "Auth Token", icon: faKey };
|
||||||
case RailwayConnectionMethod.AccountToken:
|
case RailwayConnectionMethod.AccountToken:
|
||||||
|
case SupabaseConnectionMethod.AccessToken:
|
||||||
return { name: "Account Token", icon: faKey };
|
return { name: "Account Token", icon: faKey };
|
||||||
case RailwayConnectionMethod.TeamToken:
|
case RailwayConnectionMethod.TeamToken:
|
||||||
return { name: "Team Token", icon: faKey };
|
return { name: "Team Token", icon: faKey };
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
|||||||
[SecretSync.Checkly]: {
|
[SecretSync.Checkly]: {
|
||||||
name: "Checkly",
|
name: "Checkly",
|
||||||
image: "Checkly.png"
|
image: "Checkly.png"
|
||||||
|
},
|
||||||
|
[SecretSync.Supabase]: {
|
||||||
|
name: "Supabase",
|
||||||
|
image: "Supabase.png"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,7 +128,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.GitLab]: AppConnection.Gitlab,
|
[SecretSync.GitLab]: AppConnection.Gitlab,
|
||||||
[SecretSync.CloudflarePages]: AppConnection.Cloudflare,
|
[SecretSync.CloudflarePages]: AppConnection.Cloudflare,
|
||||||
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
||||||
|
[SecretSync.Supabase]: AppConnection.Supabase,
|
||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway,
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
[SecretSync.Checkly]: AppConnection.Checkly
|
[SecretSync.Checkly]: AppConnection.Checkly
|
||||||
|
|||||||
@@ -31,5 +31,6 @@ export enum AppConnection {
|
|||||||
Bitbucket = "bitbucket",
|
Bitbucket = "bitbucket",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly"
|
Checkly = "checkly",
|
||||||
|
Supabase = "supabase"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./queries";
|
||||||
|
export * from "./types";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
import { appConnectionKeys } from "@app/hooks/api/appConnections";
|
||||||
|
|
||||||
|
import { TSupabaseProject } from "./types";
|
||||||
|
|
||||||
|
const supabaseConnectionKeys = {
|
||||||
|
all: [...appConnectionKeys.all, "supabase"] as const,
|
||||||
|
listProjects: (connectionId: string) =>
|
||||||
|
[...supabaseConnectionKeys.all, "workspace-scopes", connectionId] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSupabaseConnectionListProjects = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TSupabaseProject[],
|
||||||
|
unknown,
|
||||||
|
TSupabaseProject[],
|
||||||
|
ReturnType<typeof supabaseConnectionKeys.listProjects>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: supabaseConnectionKeys.listProjects(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ projects: TSupabaseProject[] }>(
|
||||||
|
`/api/v1/app-connections/supabase/${connectionId}/projects`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.projects;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type TSupabaseProject = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
@@ -148,6 +148,10 @@ export type TChecklyConnectionOption = TAppConnectionOptionBase & {
|
|||||||
app: AppConnection.Checkly;
|
app: AppConnection.Checkly;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TSupabaseConnectionOption = TAppConnectionOptionBase & {
|
||||||
|
app: AppConnection.Supabase;
|
||||||
|
};
|
||||||
|
|
||||||
export type TAppConnectionOption =
|
export type TAppConnectionOption =
|
||||||
| TAwsConnectionOption
|
| TAwsConnectionOption
|
||||||
| TGitHubConnectionOption
|
| TGitHubConnectionOption
|
||||||
@@ -215,4 +219,5 @@ export type TAppConnectionOptionMap = {
|
|||||||
[AppConnection.Zabbix]: TZabbixConnectionOption;
|
[AppConnection.Zabbix]: TZabbixConnectionOption;
|
||||||
[AppConnection.Railway]: TRailwayConnectionOption;
|
[AppConnection.Railway]: TRailwayConnectionOption;
|
||||||
[AppConnection.Checkly]: TChecklyConnectionOption;
|
[AppConnection.Checkly]: TChecklyConnectionOption;
|
||||||
|
[AppConnection.Supabase]: TSupabaseConnectionOption;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { TOracleDBConnection } from "./oracledb-connection";
|
|||||||
import { TPostgresConnection } from "./postgres-connection";
|
import { TPostgresConnection } from "./postgres-connection";
|
||||||
import { TRailwayConnection } from "./railway-connection";
|
import { TRailwayConnection } from "./railway-connection";
|
||||||
import { TRenderConnection } from "./render-connection";
|
import { TRenderConnection } from "./render-connection";
|
||||||
|
import { TSupabaseConnection } from "./supabase-connection";
|
||||||
import { TTeamCityConnection } from "./teamcity-connection";
|
import { TTeamCityConnection } from "./teamcity-connection";
|
||||||
import { TTerraformCloudConnection } from "./terraform-cloud-connection";
|
import { TTerraformCloudConnection } from "./terraform-cloud-connection";
|
||||||
import { TVercelConnection } from "./vercel-connection";
|
import { TVercelConnection } from "./vercel-connection";
|
||||||
@@ -99,7 +100,8 @@ export type TAppConnection =
|
|||||||
| TBitbucketConnection
|
| TBitbucketConnection
|
||||||
| TZabbixConnection
|
| TZabbixConnection
|
||||||
| TRailwayConnection
|
| TRailwayConnection
|
||||||
| TChecklyConnection;
|
| TChecklyConnection
|
||||||
|
| TSupabaseConnection;
|
||||||
|
|
||||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||||
|
|
||||||
@@ -160,4 +162,5 @@ export type TAppConnectionMap = {
|
|||||||
[AppConnection.Zabbix]: TZabbixConnection;
|
[AppConnection.Zabbix]: TZabbixConnection;
|
||||||
[AppConnection.Railway]: TRailwayConnection;
|
[AppConnection.Railway]: TRailwayConnection;
|
||||||
[AppConnection.Checkly]: TChecklyConnection;
|
[AppConnection.Checkly]: TChecklyConnection;
|
||||||
|
[AppConnection.Supabase]: TSupabaseConnection;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||||
|
|
||||||
|
export enum SupabaseConnectionMethod {
|
||||||
|
AccessToken = "access-token"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TSupabaseConnection = TRootAppConnection & {
|
||||||
|
app: AppConnection.Supabase;
|
||||||
|
method: SupabaseConnectionMethod.AccessToken;
|
||||||
|
credentials: {
|
||||||
|
instanceUrl?: string;
|
||||||
|
accessKey: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -22,7 +22,7 @@ export enum SecretSync {
|
|||||||
GitLab = "gitlab",
|
GitLab = "gitlab",
|
||||||
CloudflarePages = "cloudflare-pages",
|
CloudflarePages = "cloudflare-pages",
|
||||||
CloudflareWorkers = "cloudflare-workers",
|
CloudflareWorkers = "cloudflare-workers",
|
||||||
|
Supabase = "supabase",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly"
|
Checkly = "checkly"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { THerokuSync } from "./heroku-sync";
|
|||||||
import { THumanitecSync } from "./humanitec-sync";
|
import { THumanitecSync } from "./humanitec-sync";
|
||||||
import { TOCIVaultSync } from "./oci-vault-sync";
|
import { TOCIVaultSync } from "./oci-vault-sync";
|
||||||
import { TRailwaySync } from "./railway-sync";
|
import { TRailwaySync } from "./railway-sync";
|
||||||
|
import { TSupabaseSync } from "./supabase";
|
||||||
import { TTeamCitySync } from "./teamcity-sync";
|
import { TTeamCitySync } from "./teamcity-sync";
|
||||||
import { TTerraformCloudSync } from "./terraform-cloud-sync";
|
import { TTerraformCloudSync } from "./terraform-cloud-sync";
|
||||||
import { TVercelSync } from "./vercel-sync";
|
import { TVercelSync } from "./vercel-sync";
|
||||||
@@ -61,7 +62,8 @@ export type TSecretSync =
|
|||||||
| TCloudflareWorkersSync
|
| TCloudflareWorkersSync
|
||||||
| TZabbixSync
|
| TZabbixSync
|
||||||
| TRailwaySync
|
| TRailwaySync
|
||||||
| TChecklySync;
|
| TChecklySync
|
||||||
|
| TSupabaseSync;
|
||||||
|
|
||||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
|
export type TSupabaseSync = TRootSecretSync & {
|
||||||
|
destination: SecretSync.Supabase;
|
||||||
|
destinationConfig: {
|
||||||
|
projectId: string;
|
||||||
|
projectName: string;
|
||||||
|
};
|
||||||
|
connection: {
|
||||||
|
app: AppConnection.Supabase;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -37,6 +37,7 @@ import { OracleDBConnectionForm } from "./OracleDBConnectionForm";
|
|||||||
import { PostgresConnectionForm } from "./PostgresConnectionForm";
|
import { PostgresConnectionForm } from "./PostgresConnectionForm";
|
||||||
import { RailwayConnectionForm } from "./RailwayConnectionForm";
|
import { RailwayConnectionForm } from "./RailwayConnectionForm";
|
||||||
import { RenderConnectionForm } from "./RenderConnectionForm";
|
import { RenderConnectionForm } from "./RenderConnectionForm";
|
||||||
|
import { SupabaseConnectionForm } from "./SupabaseConnectionForm";
|
||||||
import { TeamCityConnectionForm } from "./TeamCityConnectionForm";
|
import { TeamCityConnectionForm } from "./TeamCityConnectionForm";
|
||||||
import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm";
|
import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm";
|
||||||
import { VercelConnectionForm } from "./VercelConnectionForm";
|
import { VercelConnectionForm } from "./VercelConnectionForm";
|
||||||
@@ -146,6 +147,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
|||||||
return <RailwayConnectionForm onSubmit={onSubmit} />;
|
return <RailwayConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.Checkly:
|
case AppConnection.Checkly:
|
||||||
return <ChecklyConnectionForm onSubmit={onSubmit} />;
|
return <ChecklyConnectionForm onSubmit={onSubmit} />;
|
||||||
|
case AppConnection.Supabase:
|
||||||
|
return <SupabaseConnectionForm onSubmit={onSubmit} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled App ${app}`);
|
throw new Error(`Unhandled App ${app}`);
|
||||||
}
|
}
|
||||||
@@ -248,6 +251,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
|||||||
return <RailwayConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <RailwayConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
case AppConnection.Checkly:
|
case AppConnection.Checkly:
|
||||||
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
|
case AppConnection.Supabase:
|
||||||
|
return <SupabaseConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
|
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
ModalClose,
|
||||||
|
SecretInput,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import {
|
||||||
|
SupabaseConnectionMethod,
|
||||||
|
TSupabaseConnection
|
||||||
|
} from "@app/hooks/api/appConnections/types/supabase-connection";
|
||||||
|
|
||||||
|
import {
|
||||||
|
genericAppConnectionFieldsSchema,
|
||||||
|
GenericAppConnectionsFields
|
||||||
|
} from "./GenericAppConnectionFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appConnection?: TSupabaseConnection;
|
||||||
|
onSubmit: (formData: FormData) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||||
|
app: z.literal(AppConnection.Supabase)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formSchema = z.discriminatedUnion("method", [
|
||||||
|
rootSchema.extend({
|
||||||
|
method: z.literal(SupabaseConnectionMethod.AccessToken),
|
||||||
|
credentials: z.object({
|
||||||
|
accessKey: z.string().trim().min(1, "Access Key required"),
|
||||||
|
instanceUrl: z.string().url().optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const SupabaseConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||||
|
const isUpdate = Boolean(appConnection);
|
||||||
|
|
||||||
|
const form = useForm<FormData>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: appConnection ?? {
|
||||||
|
app: AppConnection.Supabase,
|
||||||
|
method: SupabaseConnectionMethod.AccessToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{!isUpdate && <GenericAppConnectionsFields />}
|
||||||
|
<Controller
|
||||||
|
name="credentials.instanceUrl"
|
||||||
|
control={control}
|
||||||
|
shouldUnregister
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Instance URL"
|
||||||
|
tooltipClassName="max-w-sm"
|
||||||
|
tooltipText="Will default to Supabase Cloud if not specified."
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
placeholder="https://api.supabase.com"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="method"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
tooltipText={`The type of token you would like to use to connect with ${
|
||||||
|
APP_CONNECTION_MAP[AppConnection.Supabase].name
|
||||||
|
}. This field cannot be changed after creation.`}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Token Type"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
isDisabled={isUpdate}
|
||||||
|
value={value}
|
||||||
|
onValueChange={(val) => onChange(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
position="popper"
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
>
|
||||||
|
{Object.values(SupabaseConnectionMethod).map((method) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={method} key={method}>
|
||||||
|
{getAppConnectionMethodDetails(method).name}{" "}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="credentials.accessKey"
|
||||||
|
control={control}
|
||||||
|
shouldUnregister
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Access Key Value"
|
||||||
|
>
|
||||||
|
<SecretInput
|
||||||
|
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
colorSchema="secondary"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
{isUpdate ? "Update Credentials" : "Connect to Supabase"}
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -21,6 +21,7 @@ import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol";
|
|||||||
import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol";
|
import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol";
|
||||||
import { RailwaySyncDestinationCol } from "./RailwaySyncDestinationCol";
|
import { RailwaySyncDestinationCol } from "./RailwaySyncDestinationCol";
|
||||||
import { RenderSyncDestinationCol } from "./RenderSyncDestinationCol";
|
import { RenderSyncDestinationCol } from "./RenderSyncDestinationCol";
|
||||||
|
import { SupabaseSyncDestinationCol } from "./SupabaseSyncDestinationCol";
|
||||||
import { TeamCitySyncDestinationCol } from "./TeamCitySyncDestinationCol";
|
import { TeamCitySyncDestinationCol } from "./TeamCitySyncDestinationCol";
|
||||||
import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinationCol";
|
import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinationCol";
|
||||||
import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol";
|
import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol";
|
||||||
@@ -85,6 +86,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
|||||||
return <RailwaySyncDestinationCol secretSync={secretSync} />;
|
return <RailwaySyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
return <SupabaseSyncDestinationCol secretSync={secretSync} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { TSupabaseSync } from "@app/hooks/api/secretSyncs/types/supabase";
|
||||||
|
|
||||||
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
|
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TSupabaseSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SupabaseSyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
|
};
|
||||||
@@ -170,6 +170,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
primaryText = destinationConfig.accountName;
|
primaryText = destinationConfig.accountName;
|
||||||
secondaryText = "Checkly Account";
|
secondaryText = "Checkly Account";
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
primaryText = destinationConfig.projectName;
|
||||||
|
secondaryText = "Supabase Project";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSecti
|
|||||||
import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection";
|
import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection";
|
||||||
import { RailwaySyncDestinationSection } from "./RailwaySyncDestinationSection";
|
import { RailwaySyncDestinationSection } from "./RailwaySyncDestinationSection";
|
||||||
import { RenderSyncDestinationSection } from "./RenderSyncDestinationSection";
|
import { RenderSyncDestinationSection } from "./RenderSyncDestinationSection";
|
||||||
|
import { SupabaseSyncDestinationSection } from "./SupabaseSyncDestinationSection";
|
||||||
import { TeamCitySyncDestinationSection } from "./TeamCitySyncDestinationSection";
|
import { TeamCitySyncDestinationSection } from "./TeamCitySyncDestinationSection";
|
||||||
import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestinationSection";
|
import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestinationSection";
|
||||||
import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection";
|
import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection";
|
||||||
@@ -130,6 +131,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
|||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
DestinationComponents = <ChecklySyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <ChecklySyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Supabase:
|
||||||
|
DestinationComponents = <SupabaseSyncDestinationSection secretSync={secretSync} />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TSupabaseSync } from "@app/hooks/api/secretSyncs/types/supabase";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TSupabaseSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SupabaseSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
return <GenericFieldLabel label="Project">{destinationConfig.projectName}</GenericFieldLabel>;
|
||||||
|
};
|
||||||
@@ -63,6 +63,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
case SecretSync.CloudflareWorkers:
|
case SecretSync.CloudflareWorkers:
|
||||||
case SecretSync.Zabbix:
|
case SecretSync.Zabbix:
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
|
case SecretSync.Supabase:
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
AdditionalSyncOptionsComponent = null;
|
AdditionalSyncOptionsComponent = null;
|
||||||
break;
|
break;
|
||||||
|
|||||||