feature: secret sync base + aws parameter store & github

This commit is contained in:
Scott Wilson
2025-01-15 21:52:50 -08:00
parent b440e918ac
commit 473efa91f0
214 changed files with 10212 additions and 623 deletions
@@ -0,0 +1,22 @@
export enum SecretSync {
AWSParameterStore = "aws-parameter-store",
GitHub = "github"
}
export enum SecretSyncStatus {
Pending = "pending",
Running = "running",
Succeeded = "succeeded",
Failed = "failed"
}
export enum SecretSyncInitialSyncBehavior {
OverwriteDestination = "overwrite-destination",
ImportPrioritizeSource = "import-prioritize-source",
ImportPrioritizeDestination = "import-prioritize-destination"
}
export enum SecretSyncImportBehavior {
PrioritizeSource = "prioritize-source",
PrioritizeDestination = "prioritize-destination"
}
@@ -0,0 +1,4 @@
export * from "./enums";
export * from "./mutations";
export * from "./queries";
export * from "./types";
@@ -0,0 +1,116 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { secretSyncKeys } from "@app/hooks/api/secretSyncs/queries";
import {
TCreateSecretSyncDTO,
TDeleteSecretSyncDTO,
TSecretSyncResponse,
TTriggerSecretSyncImportSecretsDTO,
TTriggerSecretSyncRemoveSecretsDTO,
TTriggerSecretSyncSyncSecretsDTO,
TUpdateSecretSyncDTO
} from "@app/hooks/api/secretSyncs/types";
export const useCreateSecretSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ destination, ...params }: TCreateSecretSyncDTO) => {
const { data } = await apiRequest.post<TSecretSyncResponse>(
`/api/v1/secret-syncs/${destination}`,
params
);
return data.secretSync;
},
onSuccess: () => queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() })
});
};
export const useUpdateSecretSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, destination, ...params }: TUpdateSecretSyncDTO) => {
const { data } = await apiRequest.patch<TSecretSyncResponse>(
`/api/v1/secret-syncs/${destination}/${syncId}`,
params
);
return data.secretSync;
},
onSuccess: (_, { syncId, destination }) => {
queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() });
queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) });
}
});
};
export const useDeleteSecretSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, destination }: TDeleteSecretSyncDTO) => {
const { data } = await apiRequest.delete(`/api/v1/secret-syncs/${destination}/${syncId}`);
return data;
},
onSuccess: (_, { syncId, destination }) => {
queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() });
queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) });
}
});
};
export const useTriggerSecretSyncSyncSecrets = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, destination }: TTriggerSecretSyncSyncSecretsDTO) => {
const { data } = await apiRequest.post(
`/api/v1/secret-syncs/${destination}/${syncId}/sync-secrets`
);
return data;
},
onSuccess: (_, { syncId, destination }) => {
queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() });
queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) });
}
});
};
export const useTriggerSecretSyncImportSecrets = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
syncId,
destination,
importBehavior
}: TTriggerSecretSyncImportSecretsDTO) => {
const { data } = await apiRequest.post(
`/api/v1/secret-syncs/${destination}/${syncId}/import-secrets?importBehavior=${importBehavior}`
);
return data;
},
onSuccess: (_, { syncId, destination }) => {
queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() });
queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) });
}
});
};
export const useTriggerSecretSyncRemoveSecrets = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, destination }: TTriggerSecretSyncRemoveSecretsDTO) => {
const { data } = await apiRequest.post(
`/api/v1/secret-syncs/${destination}/${syncId}/remove-secrets`
);
return data;
},
onSuccess: (_, { syncId, destination }) => {
queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() });
queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) });
}
});
};
@@ -0,0 +1,88 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { SecretSync, TSecretSyncOption } from "@app/hooks/api/secretSyncs";
import {
TListSecretSyncOptions,
TListSecretSyncs,
TSecretSync,
TSecretSyncResponse
} from "@app/hooks/api/secretSyncs/types";
export const secretSyncKeys = {
all: ["secret-sync"] as const,
options: () => [...secretSyncKeys.all, "options"] as const,
list: () => [...secretSyncKeys.all, "list"] as const,
byId: (destination: SecretSync, syncId: string) =>
[...secretSyncKeys.all, destination, "by-id", syncId] as const
};
export const useSecretSyncOptions = (
options?: Omit<
UseQueryOptions<
TSecretSyncOption[],
unknown,
TSecretSyncOption[],
ReturnType<typeof secretSyncKeys.options>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: secretSyncKeys.options(),
queryFn: async () => {
const { data } = await apiRequest.get<TListSecretSyncOptions>("/api/v1/secret-syncs/options");
return data.secretSyncOptions;
},
...options
});
};
export const useSecretSyncOption = (destination: SecretSync) => {
const { data: syncOptions, isPending } = useSecretSyncOptions();
const syncOption = syncOptions?.find((option) => option.destination === destination);
return { syncOption, isPending };
};
export const useListSecretSyncs = (
projectId: string,
options?: Omit<
UseQueryOptions<TSecretSync[], unknown, TSecretSync[], ReturnType<typeof secretSyncKeys.list>>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: secretSyncKeys.list(),
queryFn: async () => {
const { data } = await apiRequest.get<TListSecretSyncs>("/api/v1/secret-syncs", {
params: { projectId }
});
return data.secretSyncs;
},
...options
});
};
export const useGetSecretSync = (
destination: SecretSync,
syncId: string,
options?: Omit<
UseQueryOptions<TSecretSync, unknown, TSecretSync, ReturnType<typeof secretSyncKeys.byId>>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: secretSyncKeys.byId(destination, syncId),
queryFn: async () => {
const { data } = await apiRequest.get<TSecretSyncResponse>(
`/api/v1/secret-syncs/${destination}/${syncId}`
);
return data.secretSync;
},
...options
});
};
@@ -0,0 +1,16 @@
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 TAwsParameterStoreSync = TRootSecretSync & {
destination: SecretSync.AWSParameterStore;
destinationConfig: {
path: string;
region: string;
};
connection: {
app: AppConnection.AWS;
name: string;
id: string;
};
};
@@ -0,0 +1,42 @@
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 enum GitHubSyncScope {
Organization = "organization",
Repository = "repository",
RepositoryEnvironment = "repository-environment"
}
export enum GitHubSyncVisibility {
All = "all",
Private = "private",
Selected = "selected"
}
export type TGitHubSync = TRootSecretSync & {
destination: SecretSync.GitHub;
destinationConfig:
| {
scope: GitHubSyncScope.Organization;
org: string;
visibility: GitHubSyncVisibility;
selectedRepositoryIds?: number[];
}
| {
scope: GitHubSyncScope.Repository;
owner: string;
repo: string;
}
| {
scope: GitHubSyncScope.RepositoryEnvironment;
owner: string;
repo: string;
env: string;
};
connection: {
app: AppConnection.GitHub;
name: string;
id: string;
};
};
@@ -0,0 +1,56 @@
import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs";
import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync";
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
import { DiscriminativePick } from "@app/types";
export type TSecretSyncOption = {
name: string;
destination: SecretSync;
canImportSecrets: boolean;
};
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync;
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
export type TListSecretSyncOptions = { secretSyncOptions: TSecretSyncOption[] };
export type TSecretSyncResponse = { secretSync: TSecretSync };
export type TCreateSecretSyncDTO = DiscriminativePick<
TSecretSync,
| "name"
| "destinationConfig"
| "description"
| "connectionId"
| "syncOptions"
| "destination"
| "isEnabled"
> & { environment: string; secretPath: string; projectId: string };
export type TUpdateSecretSyncDTO = Partial<
Omit<TCreateSecretSyncDTO, "connectionId" | "destination" | "projectId">
> & {
destination: SecretSync;
syncId: string;
};
export type TDeleteSecretSyncDTO = {
destination: SecretSync;
syncId: string;
};
export type TTriggerSecretSyncSyncSecretsDTO = {
destination: SecretSync;
syncId: string;
};
export type TTriggerSecretSyncImportSecretsDTO = {
destination: SecretSync;
syncId: string;
importBehavior: SecretSyncImportBehavior;
};
export type TTriggerSecretSyncRemoveSecretsDTO = {
destination: SecretSync;
syncId: string;
};
@@ -0,0 +1,46 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretSyncInitialSyncBehavior, SecretSyncStatus } from "@app/hooks/api/secretSyncs";
export type TRootSecretSync = {
id: string;
name: string;
description?: string | null;
version: number;
folderId: string;
connectionId: string;
createdAt: string;
updatedAt: string;
isEnabled: boolean;
projectId: string;
syncStatus: SecretSyncStatus | null;
lastSyncJobId: string | null;
lastSyncedAt: Date | null;
lastSyncMessage: string | null;
importStatus: SecretSyncStatus | null;
lastImportJobId: string | null;
lastImportedAt: Date | null;
lastImportMessage: string | null;
removeStatus: SecretSyncStatus | null;
lastRemoveJobId: string | null;
lastRemovedAt: Date | null;
lastRemoveMessage: string | null;
syncOptions: {
initialSyncBehavior: SecretSyncInitialSyncBehavior;
prependPrefix?: string;
appendSuffix?: string;
};
connection: {
app: AppConnection;
id: string;
name: string;
};
environment: {
id: string;
name: string;
slug: string;
};
folder: {
id: string;
path: string;
};
};