mirror of
https://github.com/Oak-and-Sprout/sprout-track.git
synced 2026-05-13 19:19:19 -05:00
added ability for env file to preserve current data setup
This commit is contained in:
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
import JSZip from 'jszip';
|
||||
import prisma from '../../db';
|
||||
import { withAuthContext, ApiResponse, AuthResult } from '../../utils/auth';
|
||||
import { reloadEnvFile, ensureEnvDefaults } from '../../utils/env-reload';
|
||||
import { reloadEnvFile, ensureEnvDefaults, parseEnvFile, replaceEnvVar } from '../../utils/env-reload';
|
||||
import { isSQLite } from '../../utils/db-provider';
|
||||
import { importFromSQLiteFile, importFromJSON } from '../../utils/db-backup';
|
||||
|
||||
@@ -99,22 +99,42 @@ async function handler(request: NextRequest, authContext: AuthResult): Promise<N
|
||||
);
|
||||
}
|
||||
|
||||
// Extract and restore .env file if it exists
|
||||
// Extract and restore .env file if it exists, preserving current DB connection params
|
||||
const envFiles = Object.keys(zip.files).filter(name => name.endsWith('.backup.env'));
|
||||
if (envFiles.length > 0) {
|
||||
const envFile = zip.file(envFiles[0]);
|
||||
if (envFile) {
|
||||
const envContent = await envFile.async('string');
|
||||
|
||||
// Backup existing .env
|
||||
// Save current DB connection params before overwriting
|
||||
const preserveKeys = ['DATABASE_PROVIDER', 'DATABASE_URL', 'LOG_DATABASE_URL'];
|
||||
const preserved: Record<string, string> = {};
|
||||
if (fs.existsSync(envPath)) {
|
||||
const currentEnvContent = fs.readFileSync(envPath, 'utf-8');
|
||||
const currentVars = parseEnvFile(currentEnvContent);
|
||||
for (const key of preserveKeys) {
|
||||
if (currentVars[key]) preserved[key] = currentVars[key];
|
||||
}
|
||||
|
||||
// Backup existing .env
|
||||
const envBackupPath = `${envPath}.backup-${dateStr}`;
|
||||
await fs.promises.copyFile(envPath, envBackupPath);
|
||||
console.log('✓ Existing .env backed up');
|
||||
}
|
||||
|
||||
// Write the backup's .env
|
||||
await fs.promises.writeFile(envPath, envContent);
|
||||
console.log('✓ .env file restored successfully');
|
||||
|
||||
// Re-apply preserved DB connection params
|
||||
if (Object.keys(preserved).length > 0) {
|
||||
let restoredContent = await fs.promises.readFile(envPath, 'utf-8');
|
||||
for (const [key, value] of Object.entries(preserved)) {
|
||||
restoredContent = replaceEnvVar(restoredContent, key, value);
|
||||
}
|
||||
await fs.promises.writeFile(envPath, restoredContent);
|
||||
console.log('✓ Database connection parameters preserved');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
import JSZip from 'jszip';
|
||||
import prisma from '../db';
|
||||
import { withSysAdminAuth, ApiResponse } from '../utils/auth';
|
||||
import { reloadEnvFile, ensureEnvDefaults } from '../utils/env-reload';
|
||||
import { reloadEnvFile, ensureEnvDefaults, parseEnvFile, replaceEnvVar } from '../utils/env-reload';
|
||||
import { isSQLite } from '../utils/db-provider';
|
||||
import { importFromSQLiteFile, importFromJSON, exportToJSON } from '../utils/db-backup';
|
||||
|
||||
@@ -143,20 +143,41 @@ async function postHandler(req: NextRequest): Promise<NextResponse<ApiResponse<a
|
||||
);
|
||||
}
|
||||
|
||||
// Extract and restore .env file if it exists
|
||||
// Extract and restore .env file if it exists, preserving current DB connection params
|
||||
const envFiles = Object.keys(zip.files).filter(name => name.endsWith('.backup.env'));
|
||||
if (envFiles.length > 0) {
|
||||
const envFile = zip.file(envFiles[0]);
|
||||
if (envFile) {
|
||||
const envContent = await envFile.async('string');
|
||||
|
||||
// Backup existing .env
|
||||
if (await fs.promises.access(envPath).then(() => true).catch(() => false)) {
|
||||
// Save current DB connection params before overwriting
|
||||
const preserveKeys = ['DATABASE_PROVIDER', 'DATABASE_URL', 'LOG_DATABASE_URL'];
|
||||
const preserved: Record<string, string> = {};
|
||||
const currentEnvExists = await fs.promises.access(envPath).then(() => true).catch(() => false);
|
||||
if (currentEnvExists) {
|
||||
const currentEnvContent = await fs.promises.readFile(envPath, 'utf-8');
|
||||
const currentVars = parseEnvFile(currentEnvContent);
|
||||
for (const key of preserveKeys) {
|
||||
if (currentVars[key]) preserved[key] = currentVars[key];
|
||||
}
|
||||
|
||||
// Backup existing .env
|
||||
const envBackupPath = `${envPath}.backup-${dateStr}`;
|
||||
await fs.promises.copyFile(envPath, envBackupPath);
|
||||
}
|
||||
|
||||
// Write the backup's .env
|
||||
await fs.promises.writeFile(envPath, envContent);
|
||||
|
||||
// Re-apply preserved DB connection params
|
||||
if (Object.keys(preserved).length > 0) {
|
||||
let restoredContent = await fs.promises.readFile(envPath, 'utf-8');
|
||||
for (const [key, value] of Object.entries(preserved)) {
|
||||
restoredContent = replaceEnvVar(restoredContent, key, value);
|
||||
}
|
||||
await fs.promises.writeFile(envPath, restoredContent);
|
||||
console.log('Database connection parameters preserved during .env restore');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { execSync } from 'child_process';
|
||||
* Parses a .env file content and returns key-value pairs
|
||||
* Handles comments, empty lines, quoted values, and unquoted values
|
||||
*/
|
||||
function parseEnvFile(content: string): Record<string, string> {
|
||||
export function parseEnvFile(content: string): Record<string, string> {
|
||||
const envVars: Record<string, string> = {};
|
||||
const lines = content.split('\n');
|
||||
|
||||
@@ -51,12 +51,20 @@ function parseEnvFile(content: string): Record<string, string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads environment variables from the .env file into process.env
|
||||
* This is useful after restoring a backup that includes a new .env file
|
||||
*
|
||||
* @param envFilePath Optional path to the .env file. Defaults to './.env'
|
||||
* @returns True if the reload was successful, false otherwise
|
||||
* Replaces or appends an environment variable in .env file content.
|
||||
* If the key exists, its value is updated in-place. If not, it's appended.
|
||||
*/
|
||||
export function replaceEnvVar(content: string, key: string, value: string): string {
|
||||
const regex = new RegExp(`^${key}=.*$`, 'm');
|
||||
const needsQuotes = value.includes(' ') || value.includes('://');
|
||||
const newLine = needsQuotes ? `${key}="${value}"` : `${key}=${value}`;
|
||||
if (regex.test(content)) {
|
||||
return content.replace(regex, newLine);
|
||||
}
|
||||
const suffix = content.endsWith('\n') ? '' : '\n';
|
||||
return content + suffix + newLine + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures all required environment variables exist in the .env file.
|
||||
* Adds missing vars with correct defaults without overwriting existing values.
|
||||
|
||||
Reference in New Issue
Block a user