Skip to content

Commit

Permalink
fix / optimizing tfvars logic
Browse files Browse the repository at this point in the history
  • Loading branch information
psiddharthdesign committed Jul 30, 2024
1 parent e0070c6 commit 215f70f
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Separator } from "@/components/ui/separator";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
import { tfvarsOnBulkUpdate, tfvarsOnDelete, tfvarsOnUpdate } from "@/data/user/tfvars";
import { EnvVar } from "@/types/userTypes";
import { motion } from 'framer-motion';
import { Copy, Edit, LockKeyhole, Plus, Save, Trash, Unlock } from 'lucide-react';
Expand All @@ -19,9 +20,7 @@ import { toast } from 'sonner';

type TFVarTableProps = {
envVars: EnvVar[];
onUpdate: (oldName: string, newName: string, value: string, isSecret: boolean) => Promise<EnvVar[]>;
onDelete: (name: string) => Promise<EnvVar[]>;
onBulkUpdate: (vars: EnvVar[]) => Promise<EnvVar[]>;
projectId: string;
};

const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable }) => {
Expand Down Expand Up @@ -51,7 +50,7 @@ const EmptyState: React.FC<{ onAddVariable: () => void }> = ({ onAddVariable })
);
};

export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }: TFVarTableProps) {
export default function TFVarTable({ projectId, envVars }: TFVarTableProps) {
const [editingVar, setEditingVar] = useState<{ originalName: string, currentVar: EnvVar } | null>(null);
const [newVar, setNewVar] = useState<Omit<EnvVar, 'updated_at'>>({ name: '', value: '', is_secret: false });
const [bulkEditMode, setBulkEditMode] = useState(false);
Expand All @@ -70,7 +69,6 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
});
};


const handleSave = async () => {
if (editingVar) {
if (editingVar.currentVar.name.toLowerCase() !== editingVar.originalName.toLowerCase() &&
Expand All @@ -80,11 +78,12 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
}
setIsLoading(true);
try {
await onUpdate(
await tfvarsOnUpdate(
editingVar.originalName,
editingVar.currentVar.name,
editingVar.currentVar.value,
editingVar.currentVar.is_secret
editingVar.currentVar.is_secret,
projectId
);
toast.success('Variable updated successfully');
setEditingVar(null);
Expand All @@ -97,7 +96,6 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
}
};


const handleAddNew = async () => {
if (newVar.name && newVar.value) {
if (envVars.some(v => v.name.toLowerCase() === newVar.name.toLowerCase())) {
Expand All @@ -106,7 +104,7 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
}
setIsLoading(true);
try {
await onUpdate(newVar.name, newVar.name, newVar.value, newVar.is_secret);
await tfvarsOnUpdate(newVar.name, newVar.name, newVar.value, newVar.is_secret, projectId);
toast.success('New variable added successfully');
setNewVar({ name: '', value: '', is_secret: false });
setShowAddForm(false);
Expand All @@ -122,7 +120,7 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
const handleDeleteVar = async (name: string) => {
setIsLoading(true);
try {
await onDelete(name);
await tfvarsOnDelete(name, projectId);
toast.success('Variable deleted successfully');
router.refresh();
} catch (error) {
Expand All @@ -136,15 +134,14 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
try {
const parsedVars = JSON.parse(bulkEditValue);
if (Array.isArray(parsedVars)) {
// Check for duplicate names in the parsed vars
const names = parsedVars.map(v => v.name.toLowerCase());
if (new Set(names).size !== names.length) {
toast.error('Duplicate variable names are not allowed');
return;
}

setIsLoading(true);
await onBulkUpdate(parsedVars);
await tfvarsOnBulkUpdate(parsedVars, projectId);
toast.success('Bulk update successful');
setBulkEditMode(false);
router.refresh();
Expand All @@ -156,7 +153,6 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
}
};


const toggleBulkEdit = () => {
if (!bulkEditMode) {
const nonSecretVars = envVars.filter(v => !v.is_secret).map(({ name, value }) => ({ name, value }));
Expand Down Expand Up @@ -345,7 +341,6 @@ export default function TFVarTable({ envVars, onUpdate, onDelete, onBulkUpdate }
Edit all environment variables at once in JSON format. Be careful with this operation.
</p>
<div className="flex gap-2">
{/* <Button variant="outline" className='w-full' onClick={handleCopyAll}>Copy All</Button> */}
<Button variant="secondary" className="w-full" onClick={toggleBulkEdit}>
{bulkEditMode ? 'Cancel Bulk Edit' : 'Bulk Edit'}
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// TFVarsDetails.tsx
'use client'

import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
Expand All @@ -8,18 +9,11 @@ import { motion } from "framer-motion";
import TFVarTable from "./TFVarTable";

type TFVarsDetailsProps = {
tfvarsdata: {
id: string;
project_id: string;
tfvars: EnvVar[];
updated_at: string;
};
onUpdate: (oldName: string, newName: string, value: string, isSecret: boolean) => Promise<EnvVar[]>;
onDelete: (name: string) => Promise<EnvVar[]>;
onBulkUpdate: (vars: EnvVar[]) => Promise<EnvVar[]>;
projectId: string;
initialEnvVars: EnvVar[];
}

export default function TFVarsDetails({ tfvarsdata, onUpdate, onDelete, onBulkUpdate }: TFVarsDetailsProps) {
export default function TFVarsDetails({ projectId, initialEnvVars }: TFVarsDetailsProps) {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
Expand All @@ -34,10 +28,8 @@ export default function TFVarsDetails({ tfvarsdata, onUpdate, onDelete, onBulkUp
</CardHeader>
<CardContent>
<TFVarTable
envVars={tfvarsdata.tfvars}
onUpdate={onUpdate}
onDelete={onDelete}
onBulkUpdate={onBulkUpdate}
projectId={projectId}
envVars={initialEnvVars}
/>
</CardContent>
<Separator />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { deleteEnvVar, getAllEnvVars, storeEncryptedEnvVar } from "@/data/admin/encryption";
// page.tsx
import { getAllEnvVars } from "@/data/admin/encryption";
import { getSlimProjectBySlug } from "@/data/user/projects";
import { EnvVar } from "@/types/userTypes";
import { projectSlugParamSchema } from "@/utils/zod-schemas/params";
import type { Metadata } from "next";
import TFVarsDetails from '../TFVarsDetails';
Expand Down Expand Up @@ -28,60 +28,13 @@ export default async function TFVarsPage({ params }: { params: unknown }) {
throw new Error('MASTER_PASSWORD or ENCRYPTION_SALT is not set');
}

const envVars = await getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);

async function handleUpdate(oldName: string, newName: string, value: string, isSecret: boolean) {
'use server'
if (oldName !== newName) {
await deleteEnvVar(project.id, oldName);
}
await storeEncryptedEnvVar(project.id, newName, value, isSecret, MASTER_PASSWORD, ENCRYPTION_SALT);
return getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
}

async function handleDelete(name: string) {
'use server'
await deleteEnvVar(project.id, name);
return getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
}

async function handleBulkUpdate(vars: EnvVar[]) {
'use server'
const currentVars = await getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
const currentVarsMap = Object.fromEntries(currentVars.map(v => [v.name, v]));

for (const newVar of vars) {
const currentVar = currentVarsMap[newVar.name];
if (currentVar) {
if (!currentVar.is_secret && (currentVar.value !== newVar.value || currentVar.name !== newVar.name)) {
await handleUpdate(currentVar.name, newVar.name, newVar.value, currentVar.is_secret);
}
} else {
await handleUpdate(newVar.name, newVar.name, newVar.value, false);
}
}

for (const currentVar of currentVars) {
if (!vars.some(v => v.name === currentVar.name) && !currentVar.is_secret) {
await deleteEnvVar(project.id, currentVar.name);
}
}

return getAllEnvVars(project.id, MASTER_PASSWORD, ENCRYPTION_SALT);
}
const envVars = await getAllEnvVars(project.id);

return (
<div className="flex flex-col space-y-4 max-w-5xl mt-2">
<TFVarsDetails
tfvarsdata={{
id: project.id,
project_id: project.id,
tfvars: envVars,
updated_at: new Date().toISOString()
}}
onUpdate={handleUpdate}
onDelete={handleDelete}
onBulkUpdate={handleBulkUpdate}
projectId={project.id}
initialEnvVars={envVars}
/>
</div>
);
Expand Down
14 changes: 2 additions & 12 deletions src/data/admin/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ export async function storeEncryptedEnvVar(
name: string,
value: string,
isSecret: boolean,
masterPassword: string,
salt: string,
) {
console.log('Encryption: Storing encrypted var:', {
projectId,
Expand Down Expand Up @@ -93,11 +91,7 @@ export async function storeEncryptedEnvVar(
console.log('Encryption: Variable stored successfully');
return data;
}
export async function getDecryptedEnvVar(
projectId: string,
name: string,
masterPassword: string,
) {
export async function getDecryptedEnvVar(projectId: string, name: string) {
const { data, error } = await supabaseAdminClient
.from('encrypted_env_vars')
.select('encrypted_value, is_secret')
Expand Down Expand Up @@ -133,11 +127,7 @@ export async function deleteEnvVar(projectId: string, name: string) {
if (error) throw error;
}

export async function getAllEnvVars(
projectId: string,
masterPassword: string,
salt: string,
): Promise<EnvVar[]> {
export async function getAllEnvVars(projectId: string): Promise<EnvVar[]> {
const { data, error } = await supabaseAdminClient
.from('encrypted_env_vars')
.select('name, encrypted_value, is_secret, updated_at')
Expand Down
84 changes: 84 additions & 0 deletions src/data/user/tfvars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// tfvars.ts
'use server';

import { EnvVar } from '@/types/userTypes';
import {
deleteEnvVar,
getAllEnvVars,
storeEncryptedEnvVar,
} from '../admin/encryption';

export async function tfvarsOnUpdate(
oldName: string,
newName: string,
value: string,
isSecret: boolean,
projectId: string,
): Promise<EnvVar[]> {
if (oldName !== newName) {
await deleteEnvVar(projectId, oldName);
}
await storeEncryptedEnvVar(projectId, newName, value, isSecret);
const vars = await getAllEnvVars(projectId);
return vars.map((v) => ({ ...v, updated_at: new Date().toISOString() }));
}

export async function tfvarsOnDelete(
name: string,
projectId: string,
): Promise<EnvVar[]> {
await deleteEnvVar(projectId, name);
const vars = await getAllEnvVars(projectId);
return vars.map((v) => ({ ...v, updated_at: new Date().toISOString() }));
}

export async function tfvarsOnBulkUpdate(
vars: EnvVar[],
projectId: string,
): Promise<EnvVar[]> {
const currentVars = await getAllEnvVars(projectId);
const currentVarsMap = Object.fromEntries(
currentVars.map((v) => [v.name, v]),
);

for (const newVar of vars) {
const currentVar = currentVarsMap[newVar.name];
if (currentVar) {
if (
!currentVar.is_secret &&
(currentVar.value !== newVar.value || currentVar.name !== newVar.name)
) {
await tfvarsOnUpdate(
currentVar.name,
newVar.name,
newVar.value,
currentVar.is_secret,
projectId,
);
}
} else {
await tfvarsOnUpdate(
newVar.name,
newVar.name,
newVar.value,
false,
projectId,
);
}
}

for (const currentVar of currentVars) {
if (
!vars.some((v) => v.name === currentVar.name) &&
!currentVar.is_secret
) {
await tfvarsOnDelete(currentVar.name, projectId);
}
}

const updatedVars = await getAllEnvVars(projectId);
return updatedVars.map((v) => ({
...v,
updated_at: new Date().toISOString(),
}));
}

0 comments on commit 215f70f

Please sign in to comment.