Skip to content

Commit

Permalink
Docker setup in Setup Script (#3187)
Browse files Browse the repository at this point in the history
* first commit

* migrated jest to vitest in validateRecaptcha
  • Loading branch information
VanshikaSabharwal authored Jan 6, 2025
1 parent c8a1290 commit 2f68501
Show file tree
Hide file tree
Showing 10 changed files with 507 additions and 150 deletions.
203 changes: 53 additions & 150 deletions setup.ts
Original file line number Diff line number Diff line change
@@ -1,165 +1,48 @@
import dotenv from 'dotenv';
import fs from 'fs';
import inquirer from 'inquirer';
import { checkConnection } from './src/setup/checkConnection/checkConnection';
import { askForTalawaApiUrl } from './src/setup/askForTalawaApiUrl/askForTalawaApiUrl';
import { checkEnvFile } from './src/setup/checkEnvFile/checkEnvFile';
import { validateRecaptcha } from './src/setup/validateRecaptcha/validateRecaptcha';
import { askForCustomPort } from './src/setup/askForCustomPort/askForCustomPort';

export async function main(): Promise<void> {
console.log('Welcome to the Talawa Admin setup! 🚀');

if (!fs.existsSync('.env')) {
fs.openSync('.env', 'w');
const config = dotenv.parse(fs.readFileSync('.env.example'));
for (const key in config) {
fs.appendFileSync('.env', `${key}=${config[key]}\n`);
}
} else {
checkEnvFile();
}

let shouldSetCustomPort: boolean;

if (process.env.PORT) {
console.log(
`\nCustom port for development server already exists with the value:\n${process.env.PORT}`,
);
shouldSetCustomPort = true;
} else {
const { shouldSetCustomPortResponse } = await inquirer.prompt({
import askAndSetDockerOption from './src/setup/askAndSetDockerOption/askAndSetDockerOption';
import updateEnvFile from './src/setup/updateEnvFile/updateEnvFile';
import askAndUpdatePort from './src/setup/askAndUpdatePort/askAndUpdatePort';
import { askAndUpdateTalawaApiUrl } from './src/setup/askForDocker/askForDocker';

// Ask and set up reCAPTCHA
const askAndSetRecaptcha = async (): Promise<void> => {
try {
const { shouldUseRecaptcha } = await inquirer.prompt({
type: 'confirm',
name: 'shouldSetCustomPortResponse',
message: 'Would you like to set up a custom port?',
name: 'shouldUseRecaptcha',
message: 'Would you like to set up reCAPTCHA?',
default: true,
});
shouldSetCustomPort = shouldSetCustomPortResponse;
}

if (shouldSetCustomPort) {
const customPort = await askForCustomPort();

const port = dotenv.parse(fs.readFileSync('.env')).PORT;

fs.readFile('.env', 'utf8', (err, data) => {
const result = data.replace(`PORT=${port}`, `PORT=${customPort}`);
fs.writeFileSync('.env', result, 'utf8');
});
}

let shouldSetTalawaApiUrl: boolean;

if (process.env.REACT_APP_TALAWA_URL) {
console.log(
`\nEndpoint for accessing talawa-api graphql service already exists with the value:\n${process.env.REACT_APP_TALAWA_URL}`,
);
shouldSetTalawaApiUrl = true;
} else {
const { shouldSetTalawaApiUrlResponse } = await inquirer.prompt({
type: 'confirm',
name: 'shouldSetTalawaApiUrlResponse',
message: 'Would you like to set up talawa-api endpoint?',
default: true,
});
shouldSetTalawaApiUrl = shouldSetTalawaApiUrlResponse;
}

if (shouldSetTalawaApiUrl) {
let isConnected = false,
endpoint = '';

while (!isConnected) {
endpoint = await askForTalawaApiUrl();
const url = new URL(endpoint);
isConnected = await checkConnection(url.origin);
}
const envPath = '.env';
const currentEnvContent = fs.readFileSync(envPath, 'utf8');
const talawaApiUrl = dotenv.parse(currentEnvContent).REACT_APP_TALAWA_URL;

const updatedEnvContent = currentEnvContent.replace(
`REACT_APP_TALAWA_URL=${talawaApiUrl}`,
`REACT_APP_TALAWA_URL=${endpoint}`,
);

fs.writeFileSync(envPath, updatedEnvContent, 'utf8');
const websocketUrl = endpoint.replace(/^http(s)?:\/\//, 'ws$1://');
const currentWebSocketUrl =
dotenv.parse(updatedEnvContent).REACT_APP_BACKEND_WEBSOCKET_URL;

const finalEnvContent = updatedEnvContent.replace(
`REACT_APP_BACKEND_WEBSOCKET_URL=${currentWebSocketUrl}`,
`REACT_APP_BACKEND_WEBSOCKET_URL=${websocketUrl}`,
);

fs.writeFileSync(envPath, finalEnvContent, 'utf8');
}

const { shouldUseRecaptcha } = await inquirer.prompt({
type: 'confirm',
name: 'shouldUseRecaptcha',
message: 'Would you like to set up ReCAPTCHA?',
default: true,
});

if (shouldUseRecaptcha) {
const useRecaptcha = dotenv.parse(
fs.readFileSync('.env'),
).REACT_APP_USE_RECAPTCHA;

fs.readFile('.env', 'utf8', (err, data) => {
const result = data.replace(
`REACT_APP_USE_RECAPTCHA=${useRecaptcha}`,
`REACT_APP_USE_RECAPTCHA=yes`,
);
fs.writeFileSync('.env', result, 'utf8');
});
let shouldSetRecaptchaSiteKey: boolean;
if (process.env.REACT_APP_RECAPTCHA_SITE_KEY) {
console.log(
`\nreCAPTCHA site key already exists with the value ${process.env.REACT_APP_RECAPTCHA_SITE_KEY}`,
);
shouldSetRecaptchaSiteKey = true;
} else {
const { shouldSetRecaptchaSiteKeyResponse } = await inquirer.prompt({
type: 'confirm',
name: 'shouldSetRecaptchaSiteKeyResponse',
message: 'Would you like to set up a reCAPTCHA site key?',
default: true,
});
shouldSetRecaptchaSiteKey = shouldSetRecaptchaSiteKeyResponse;
}

if (shouldSetRecaptchaSiteKey) {
if (shouldUseRecaptcha) {
const { recaptchaSiteKeyInput } = await inquirer.prompt([
{
type: 'input',
name: 'recaptchaSiteKeyInput',
message: 'Enter your reCAPTCHA site key:',
validate: async (input: string): Promise<boolean | string> => {
if (validateRecaptcha(input)) {
return true;
}
return 'Invalid reCAPTCHA site key. Please try again.';
validate: (input: string): boolean | string => {
return (
validateRecaptcha(input) ||
'Invalid reCAPTCHA site key. Please try again.'
);
},
},
]);

const recaptchaSiteKey = dotenv.parse(
fs.readFileSync('.env'),
).REACT_APP_RECAPTCHA_SITE_KEY;

fs.readFile('.env', 'utf8', (err, data) => {
const result = data.replace(
`REACT_APP_RECAPTCHA_SITE_KEY=${recaptchaSiteKey}`,
`REACT_APP_RECAPTCHA_SITE_KEY=${recaptchaSiteKeyInput}`,
);
fs.writeFileSync('.env', result, 'utf8');
});
updateEnvFile('REACT_APP_RECAPTCHA_SITE_KEY', recaptchaSiteKeyInput);
}
} catch (error) {
console.error('Error setting up reCAPTCHA:', error);
throw new Error(`Failed to set up reCAPTCHA: ${(error as Error).message}`);
}
};

// Ask and set up logging errors in the console
const askAndSetLogErrors = async (): Promise<void> => {
const { shouldLogErrors } = await inquirer.prompt({
type: 'confirm',
name: 'shouldLogErrors',
Expand All @@ -169,17 +52,37 @@ export async function main(): Promise<void> {
});

if (shouldLogErrors) {
const logErrors = dotenv.parse(fs.readFileSync('.env')).ALLOW_LOGS;

fs.readFile('.env', 'utf8', (err, data) => {
const result = data.replace(`ALLOW_LOGS=${logErrors}`, 'ALLOW_LOGS=YES');
fs.writeFileSync('.env', result, 'utf8');
});
updateEnvFile('ALLOW_LOGS', 'YES');
}
};

console.log(
'\nCongratulations! Talawa Admin has been successfully setup! 🥂🎉',
);
// Main function to run the setup process
export async function main(): Promise<void> {
try {
console.log('Welcome to the Talawa Admin setup! 🚀');

checkEnvFile();
await askAndSetDockerOption();
const envConfig = dotenv.parse(fs.readFileSync('.env', 'utf8'));
const useDocker = envConfig.USE_DOCKER === 'YES';

// Only run these commands if Docker is NOT used
if (!useDocker) {
await askAndUpdatePort();
await askAndUpdateTalawaApiUrl();
}

await askAndSetRecaptcha();
await askAndSetLogErrors();

console.log(
'\nCongratulations! Talawa Admin has been successfully set up! 🥂🎉',
);
} catch (error) {
console.error('\n❌ Setup failed:', error);
console.log('\nPlease try again or contact support if the issue persists.');
process.exit(1);
}
}

main();
61 changes: 61 additions & 0 deletions src/setup/askAndSetDockerOption/askAndSetDockerOption.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';

// Mock modules
vi.mock('inquirer', () => ({
default: {
prompt: vi.fn(),
},
}));

vi.mock('setup/updateEnvFile/updateEnvFile', () => ({
default: vi.fn(),
}));

vi.mock('setup/askForDocker/askForDocker', () => ({
askForDocker: vi.fn(),
}));

// Import after mocking
import askAndSetDockerOption from './askAndSetDockerOption';
import inquirer from 'inquirer';
import updateEnvFile from 'setup/updateEnvFile/updateEnvFile';
import { askForDocker } from 'setup/askForDocker/askForDocker';

describe('askAndSetDockerOption', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('should set up Docker when user selects yes', async () => {
(inquirer.prompt as unknown as jest.Mock).mockResolvedValueOnce({
useDocker: true,
});
(askForDocker as jest.Mock).mockResolvedValueOnce(8080);

await askAndSetDockerOption();

expect(updateEnvFile).toHaveBeenCalledWith('USE_DOCKER', 'YES');
expect(updateEnvFile).toHaveBeenCalledWith('DOCKER_PORT', 8080);
});

it('should set up without Docker when user selects no', async () => {
(inquirer.prompt as unknown as jest.Mock).mockResolvedValueOnce({
useDocker: false,
});

await askAndSetDockerOption();

expect(updateEnvFile).toHaveBeenCalledWith('USE_DOCKER', 'NO');
});

it('should handle errors when askForDocker fails', async () => {
(inquirer.prompt as unknown as jest.Mock).mockResolvedValueOnce({
useDocker: true,
});
(askForDocker as jest.Mock).mockRejectedValueOnce(
new Error('Docker error'),
);

await expect(askAndSetDockerOption()).rejects.toThrow('Docker error');
});
});
35 changes: 35 additions & 0 deletions src/setup/askAndSetDockerOption/askAndSetDockerOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import inquirer from 'inquirer';
import updateEnvFile from 'setup/updateEnvFile/updateEnvFile';
import { askForDocker } from 'setup/askForDocker/askForDocker';

// Function to manage Docker setup
const askAndSetDockerOption = async (): Promise<void> => {
const { useDocker } = await inquirer.prompt({
type: 'confirm',
name: 'useDocker',
message: 'Would you like to set up with Docker?',
default: false,
});

if (useDocker) {
console.log('Setting up with Docker...');
updateEnvFile('USE_DOCKER', 'YES');
const answers = await askForDocker();
const DOCKER_PORT_NUMBER = answers;
updateEnvFile('DOCKER_PORT', DOCKER_PORT_NUMBER);

const DOCKER_NAME = 'talawa-admin';
console.log(`
Run the commands below after setup:-
1. docker build -t ${DOCKER_NAME} .
2. docker run -d -p ${DOCKER_PORT_NUMBER}:${DOCKER_PORT_NUMBER} ${DOCKER_NAME}
`);
} else {
console.log('Setting up without Docker...');
updateEnvFile('USE_DOCKER', 'NO');
}
};

export default askAndSetDockerOption;
25 changes: 25 additions & 0 deletions src/setup/askAndUpdatePort/askAndUpdatePort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import updateEnvFile from 'setup/updateEnvFile/updateEnvFile';
import { askForCustomPort } from 'setup/askForCustomPort/askForCustomPort';
import inquirer from 'inquirer';

// Ask and update the custom port
const askAndUpdatePort = async (): Promise<void> => {
const { shouldSetCustomPortResponse } = await inquirer.prompt({
type: 'confirm',
name: 'shouldSetCustomPortResponse',
message:
'Would you like to set up a custom port for running Talawa Admin without Docker?',
default: true,
});

if (shouldSetCustomPortResponse) {
const customPort = await askForCustomPort();
if (customPort < 1024 || customPort > 65535) {
throw new Error('Port must be between 1024 and 65535');
}

updateEnvFile('PORT', String(customPort));
}
};

export default askAndUpdatePort;
Loading

0 comments on commit 2f68501

Please sign in to comment.