diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 90781b58ca..55cd02fff2 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -467,4 +467,42 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) => return res.status(200).send({ membership }); -} \ No newline at end of file +} + +/** + * Change autoCapitilzation Rule of workspace + * @param req + * @param res + * @returns + */ +export const toggleAutoCapitalization = async (req: Request, res: Response) => { + let workspace; + try { + const { workspaceId } = req.params; + const { autoCapitalization } = req.body; + + workspace = await Workspace.findOneAndUpdate( + { + _id: workspaceId + }, + { + autoCapitalization + }, + { + new: true + } + ); + } catch (err) { + Sentry.setUser({ email: req.user.email }); + Sentry.captureException(err); + return res.status(400).send({ + message: 'Failed to change autoCapitalization setting' + }); + } + + return res.status(200).send({ + message: 'Successfully changed autoCapitalization setting', + workspace + }); +}; + diff --git a/backend/src/models/workspace.ts b/backend/src/models/workspace.ts index a70b64340e..d68ae9416b 100644 --- a/backend/src/models/workspace.ts +++ b/backend/src/models/workspace.ts @@ -8,6 +8,7 @@ export interface IWorkspace { name: string; slug: string; }>; + autoCapitalization: boolean; } const workspaceSchema = new Schema({ @@ -15,6 +16,10 @@ const workspaceSchema = new Schema({ type: String, required: true }, + autoCapitalization: { + type: Boolean, + default: true, + }, organization: { type: Schema.Types.ObjectId, ref: 'Organization', diff --git a/backend/src/routes/v2/workspace.ts b/backend/src/routes/v2/workspace.ts index 55299e575f..9a753badd7 100644 --- a/backend/src/routes/v2/workspace.ts +++ b/backend/src/routes/v2/workspace.ts @@ -118,4 +118,19 @@ router.delete( // TODO - rewire dashboard to this route workspaceController.deleteWorkspaceMembership ); + +router.patch( + '/:workspaceId/auto-capitalization', + requireAuth({ + acceptedAuthModes: ['jwt'] + }), + requireWorkspaceAuth({ + acceptedRoles: [ADMIN, MEMBER] + }), + param('workspaceId').exists().trim(), + body('autoCapitalization').exists().trim().notEmpty(), + validateRequest, + workspaceController.toggleAutoCapitalization +); + export default router; diff --git a/frontend/public/locales/en/settings-project.json b/frontend/public/locales/en/settings-project.json index 03e4b14594..3f3401f2e4 100644 --- a/frontend/public/locales/en/settings-project.json +++ b/frontend/public/locales/en/settings-project.json @@ -9,5 +9,7 @@ "project-id-description": "To integrate Infisical into your code base and get automatic injection of environmental variables, you should use the following Project ID.", "project-id-description2": "For more guidance, including code snipets for various languages and frameworks, see ", "auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.", - "docs": "Infisical Docs" + "docs": "Infisical Docs", + "auto-capitalization": "Auto Capitalization", + "auto-capitalization-description": "According to standards, Infisical will automatically capitalize your keys. If you want to disable this feature, you can do so here." } diff --git a/frontend/src/components/dashboard/DashboardInputField.tsx b/frontend/src/components/dashboard/DashboardInputField.tsx index d64658b67d..b02ecceca1 100644 --- a/frontend/src/components/dashboard/DashboardInputField.tsx +++ b/frontend/src/components/dashboard/DashboardInputField.tsx @@ -14,6 +14,7 @@ interface DashboardInputFieldProps { type: 'varName' | 'value' | 'comment'; blurred?: boolean; isDuplicate?: boolean; + isCapitalized?: boolean; overrideEnabled?: boolean; modifyValueOverride?: (value: string | undefined, position: number) => void; isSideBarOpen?: boolean; @@ -41,6 +42,7 @@ const DashboardInputField = ({ value, blurred, isDuplicate, + isCapitalized, overrideEnabled, modifyValueOverride, isSideBarOpen @@ -69,7 +71,7 @@ const DashboardInputField = ({ }`} > onChangeHandler(e.target.value.toUpperCase(), position)} + onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, position)} type={type} value={value} className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${ @@ -230,6 +232,7 @@ function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInput prev.type === next.type && prev.position === next.position && prev.blurred === next.blurred && + prev.isCapitalized === next.isCapitalized && prev.overrideEnabled === next.overrideEnabled && prev.isDuplicate === next.isDuplicate && prev.isSideBarOpen === next.isSideBarOpen diff --git a/frontend/src/components/dashboard/KeyPair.tsx b/frontend/src/components/dashboard/KeyPair.tsx index 27409a57a6..03d22d39c3 100644 --- a/frontend/src/components/dashboard/KeyPair.tsx +++ b/frontend/src/components/dashboard/KeyPair.tsx @@ -18,6 +18,7 @@ interface KeyPairProps { toggleSidebar: (id: string) => void; sidebarSecretId: string; isSnapshot: boolean; + isCapitalized: boolean; deleteRow?: (props: DeleteRowFunctionProps) => void; tags: Tag[]; togglePITSidebar?: (value: boolean) => void; @@ -75,6 +76,7 @@ const KeyPair = ({ isDuplicate, toggleSidebar, sidebarSecretId, + isCapitalized, isSnapshot, deleteRow, togglePITSidebar, @@ -97,6 +99,7 @@ const KeyPair = ({
{keyPair.pos + 1}
{ }); }; +export const useToggleAutoCapitalization = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({ + mutationFn: ({ workspaceID, state }) => + apiRequest.patch(`/api/v2/workspace/${workspaceID}/auto-capitalization`, { autoCapitalization: state }), + onSuccess: () => { + queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); + } + }); +}; + export const useDeleteWorkspace = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index acca75dab9..312a811d97 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -3,6 +3,7 @@ export type Workspace = { _id: string; name: string; organization: string; + autoCapitalization: boolean; environments: WorkspaceEnv[]; }; @@ -11,6 +12,7 @@ export type WorkspaceTag = { _id: string; name: string; slug: string }; // mutation dto export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; +export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean }; export type DeleteWorkspaceDTO = { workspaceID: string }; diff --git a/frontend/src/pages/api/workspace/getWorkspaces.ts b/frontend/src/pages/api/workspace/getWorkspaces.ts index 95c9770088..168f6df62d 100644 --- a/frontend/src/pages/api/workspace/getWorkspaces.ts +++ b/frontend/src/pages/api/workspace/getWorkspaces.ts @@ -4,6 +4,7 @@ interface Workspace { __v: number; _id: string; name: string; + autoCapitalization: boolean; organization: string; environments: Array<{ name: string; slug: string }>; } diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index 1b668e6e49..a4204a4419 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -132,6 +132,7 @@ export default function Dashboard() { const [snapshotData, setSnapshotData] = useState(); const [numSnapshots, setNumSnapshots] = useState(); const [saveLoading, setSaveLoading] = useState(false); + const [autoCapitalization, setAutoCapitalization] = useState(false); const [dropZoneData, setDropZoneData] = useState(); const [projectTags, setProjectTags] = useState([]); @@ -224,6 +225,7 @@ export default function Dashboard() { if (!workspace) { router.push(`/dashboard/${userWorkspaces?.[0]?._id}`); } + setAutoCapitalization(workspace?.autoCapitalization ?? true); const accessibleEnvironments = await getWorkspaceEnvironments({ workspaceId }); setWorkspaceEnvs(accessibleEnvironments || []); @@ -874,6 +876,7 @@ export default function Dashboard() { .filter((row) => !sharedToHide.includes(row.id)) .map((keyPair) => ( ( { {t('common:head-title', { title: t('settings-project:title') })} + ); diff --git a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx index 9c08e2bcd0..1b30eb4e95 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx @@ -27,10 +27,15 @@ import { useGetUserWsServiceTokens, useGetWsTags, useRenameWorkspace, + useToggleAutoCapitalization, useUpdateWsEnvironment } from '@app/hooks/api'; + +import { AutoCapitalizationSection } from './components/AutoCapitalizationSection/AutoCapitalizationSection'; + import { SecretTagsSection } from './components/SecretTagsSection'; + import { CopyProjectIDSection, CreateServiceToken, @@ -55,6 +60,8 @@ export const ProjectSettingsPage = () => { const [isDeleting, setIsDeleting] = useToggle(); const renameWorkspace = useRenameWorkspace(); + const toggleAutoCapitalization = useToggleAutoCapitalization(); + const deleteWorkspace = useDeleteWorkspace(); // env crud operation const createWsEnv = useCreateWsEnvironment(); @@ -93,6 +100,26 @@ export const ProjectSettingsPage = () => { } }; + const onAutoCapitalizationToggle = async (state: boolean) => { + try { + await toggleAutoCapitalization.mutateAsync({ + workspaceID, + state + }); + const text = `Successfully ${state ? 'enabled' : 'disabled'} auto capitalization`; + createNotification({ + text, + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to update auto capitalization', + type: 'error' + }); + } + }; + const onDeleteWorkspace = async () => { setIsDeleting.on(); try { @@ -289,6 +316,10 @@ export const ProjectSettingsPage = () => { workspaceName={currentWorkspace?.name} onProjectNameChange={onRenameWorkspace} /> + Promise; +}; + +export const AutoCapitalizationSection = ({ + workspaceAutoCapitalization, + onAutoCapitalizationChange +}: Props) => { + const { t } = useTranslation(); + return ( +
+
+

+ {t('settings-project:auto-capitalization')} +

+ { + onAutoCapitalizationChange(state as boolean); + }} + > + {t('settings-project:auto-capitalization-description')} + +
+
+ ); +}; diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/index.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/index.tsx new file mode 100644 index 0000000000..2b851428e2 --- /dev/null +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/AutoCapitalizationSection/index.tsx @@ -0,0 +1 @@ +export { AutoCapitalizationSection } from './AutoCapitalizationSection'; \ No newline at end of file