refactor(server): use proxy mechanisms to remove server_url config

This commit is contained in:
Amruth Pillai 2022-03-08 10:36:04 +01:00
parent e52edaa552
commit 5a2594eb88
No known key found for this signature in database
GPG Key ID: E3C57DF9B80855AD
18 changed files with 76 additions and 82 deletions

View File

@ -3,8 +3,7 @@ TZ=UTC
SECRET_KEY=change-me
# URLs
PUBLIC_APP_URL=http://localhost:3000
PUBLIC_SERVER_URL=http://localhost:3100
PUBLIC_URL=http://localhost:3000
# Database
POSTGRES_HOST=localhost

View File

@ -15,6 +15,19 @@ const nextConfig = {
domains: ['www.gravatar.com'],
},
async rewrites() {
if (process.env.NODE_ENV === 'development') {
return [
{
source: '/api/:path*',
destination: 'http://localhost:3100/api/:path*',
},
];
}
return [];
},
// Hack to make Tailwind darkMode 'class' strategy with CSS Modules
// Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156
webpack: (config) => {

View File

@ -16,7 +16,7 @@ import Page from '@/components/build/Center/Page';
import { ServerError } from '@/services/axios';
import { printResumeAsPdf, PrintResumeAsPdfParams } from '@/services/printer';
import { fetchResumeByShortId } from '@/services/resume';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { useAppDispatch } from '@/store/hooks';
import { setResume } from '@/store/resume/resumeSlice';
import styles from '@/styles/pages/Preview.module.scss';
@ -26,34 +26,18 @@ type QueryParams = {
type Props = {
shortId: string;
resume?: Resume;
};
export const getServerSideProps: GetServerSideProps<Props> = async ({ query, locale = 'en' }) => {
const { shortId } = query as QueryParams;
try {
const resume = await fetchResumeByShortId({ shortId });
return { props: { shortId, resume, ...(await serverSideTranslations(locale, ['common'])) } };
} catch {
return { props: { shortId, ...(await serverSideTranslations(locale, ['common'])) } };
}
return { props: { shortId, ...(await serverSideTranslations(locale, ['common'])) } };
};
const Preview: NextPage<Props> = ({ shortId, resume: initialData }) => {
const Preview: NextPage<Props> = ({ shortId }) => {
const dispatch = useAppDispatch();
const resume = useAppSelector((state) => state.resume);
useEffect(() => {
if (initialData && !isEmpty(initialData)) {
dispatch(setResume(initialData));
}
}, [dispatch, initialData]);
useQuery<Resume>(`resume/${shortId}`, () => fetchResumeByShortId({ shortId }), {
initialData,
const { data: resume } = useQuery<Resume>(`resume/${shortId}`, () => fetchResumeByShortId({ shortId }), {
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
@ -64,7 +48,11 @@ const Preview: NextPage<Props> = ({ shortId, resume: initialData }) => {
const { mutateAsync, isLoading } = useMutation<string, ServerError, PrintResumeAsPdfParams>(printResumeAsPdf);
if (isEmpty(resume)) return null;
useEffect(() => {
if (resume) dispatch(setResume(resume));
}, [resume, dispatch]);
if (!resume || isEmpty(resume)) return null;
const layout: string[][][] = get(resume, 'metadata.layout', []);

View File

@ -1,4 +1,3 @@
import env from '@beam-australia/react-env';
import _axios, { AxiosError } from 'axios';
import Router from 'next/router';
@ -13,13 +12,7 @@ export type ServerError = {
path: string;
};
const axios = _axios.create({
baseURL: `${env('SERVER_URL')}/api`,
});
export const uninterceptedAxios = _axios.create({
baseURL: `${env('SERVER_URL')}/api`,
});
const axios = _axios.create({ baseURL: '/api' });
axios.interceptors.request.use((config) => {
const { accessToken } = store.getState().auth;

View File

@ -2,6 +2,8 @@ import { Resume } from '@reactive-resume/schema';
import { AxiosResponse } from 'axios';
import isEmpty from 'lodash/isEmpty';
import isBrowser from '@/utils/isBrowser';
import axios from './axios';
export type CreateResumeParams = {
@ -69,9 +71,10 @@ export const fetchResumeByIdentifier = async ({
slug,
options = { secretKey: '' },
}: FetchResumeByIdentifierParams) => {
const prefix = !isBrowser && process.env.NODE_ENV === 'development' ? 'http://localhost:3100/api' : '';
const requestOptions = isEmpty(options.secretKey) ? {} : { params: { secretKey: options.secretKey } };
return axios.get<Resume>(`/resume/${username}/${slug}`, requestOptions).then((res) => res.data);
return axios.get<Resume>(`${prefix}/resume/${username}/${slug}`, requestOptions).then((res) => res.data);
};
export const createResume = (createResumeParams: CreateResumeParams) =>

View File

@ -1,3 +1,4 @@
import env from '@beam-australia/react-env';
import { Resume } from '@reactive-resume/schema';
import get from 'lodash/get';
@ -19,7 +20,7 @@ const getResumeUrl = (resume: Resume, options: Options = defaultOptions): string
const slug: string = get(resume, 'slug');
let url = '';
let hostname = '';
let hostname = env('URL');
if (typeof window !== 'undefined') {
hostname = window.location.origin;

View File

@ -7,8 +7,6 @@ services:
env_file: .env
ports:
- 3100:3100
depends_on:
- postgres
client:
image: amruthpillai/reactive-resume:client-latest
@ -18,6 +16,3 @@ services:
- 3000:3000
depends_on:
- server
volumes:
pgdata:

View File

@ -11,29 +11,29 @@ services:
- ./scripts/database/initialize.sql:/docker-entrypoint-initdb.d/initialize.sql
- pgdata:/var/lib/postgresql/data
server:
build:
context: .
dockerfile: server/Dockerfile
container_name: server
env_file: .env
environment:
- POSTGRES_HOST=postgres
ports:
- 3100:3100
depends_on:
- postgres
# server:
# build:
# context: .
# dockerfile: server/Dockerfile
# container_name: server
# env_file: .env
# environment:
# - POSTGRES_HOST=postgres
# ports:
# - 3100:3100
# depends_on:
# - postgres
client:
build:
context: .
dockerfile: client/Dockerfile
container_name: client
env_file: .env
ports:
- 3000:3000
depends_on:
- server
# client:
# build:
# context: .
# dockerfile: client/Dockerfile
# container_name: client
# env_file: .env
# ports:
# - 3000:3000
# depends_on:
# - server
volumes:
pgdata:

View File

@ -192,6 +192,7 @@ importers:
'@reactive-resume/schema': workspace:*
'@sendgrid/mail': ^7.6.1
'@types/bcrypt': ^5.0.0
'@types/cookie-parser': ^1.4.2
'@types/express': ^4.17.13
'@types/multer': ^1.4.7
'@types/node': ^17.0.21
@ -271,6 +272,7 @@ importers:
'@nestjs/schematics': 8.0.7_typescript@4.5.5
'@reactive-resume/schema': link:../schema
'@types/bcrypt': 5.0.0
'@types/cookie-parser': 1.4.2
'@types/express': 4.17.13
'@types/multer': 1.4.7
'@types/node': 17.0.21
@ -1965,6 +1967,12 @@ packages:
dependencies:
'@types/node': 17.0.21
/@types/cookie-parser/1.4.2:
resolution: {integrity: sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==}
dependencies:
'@types/express': 4.17.13
dev: true
/@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies:

View File

@ -53,6 +53,7 @@
"@nestjs/schematics": "^8.0.7",
"@reactive-resume/schema": "workspace:*",
"@types/bcrypt": "^5.0.0",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.13",
"@types/multer": "^1.4.7",
"@types/node": "^17.0.21",

View File

@ -18,6 +18,7 @@ import { UsersModule } from './users/users.module';
@Module({
imports: [
ServeStaticModule.forRoot({
serveRoot: '/api',
rootPath: join(__dirname, 'assets'),
}),
ConfigModule,

View File

@ -5,6 +5,5 @@ export default registerAs('app', () => ({
environment: process.env.NODE_ENV,
secretKey: process.env.SECRET_KEY,
port: parseInt(process.env.PORT, 10) || 3100,
url: process.env.PUBLIC_APP_URL || 'http://localhost:3000',
serverUrl: process.env.PUBLIC_SERVER_URL || 'http://localhost:3100',
url: process.env.PUBLIC_URL || 'http://localhost:3000',
}));

View File

@ -16,8 +16,7 @@ const validationSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production').default('development'),
// URLs
PUBLIC_APP_URL: Joi.string().default('http://localhost:3000'),
PUBLIC_SERVER_URL: Joi.string().default('http://localhost:3100'),
PUBLIC_URL: Joi.string().default('http://localhost:3000'),
// Database
POSTGRES_HOST: Joi.string().required(),
@ -38,6 +37,9 @@ const validationSchema = Joi.object({
// SendGrid
SENDGRID_API_KEY: Joi.string().allow(''),
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID: Joi.string().allow(''),
SENDGRID_FROM_NAME: Joi.string().allow(''),
SENDGRID_FROM_EMAIL: Joi.string().allow(''),
});
@Module({

View File

@ -3,7 +3,6 @@ import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import cookieParser from 'cookie-parser';
import { join } from 'path';
import { AppModule } from './app.module';
@ -15,24 +14,18 @@ const bootstrap = async () => {
app.setGlobalPrefix(globalPrefix);
// Middleware
app.enableCors({ credentials: true });
app.enableShutdownHooks();
app.use(cookieParser());
// Pipes
app.useGlobalPipes(new ValidationPipe({ transform: true }));
// Email Templates
app.setBaseViewsDir(join(__dirname, 'mail/templates'));
app.setViewEngine('hbs');
const configService = app.get(ConfigService);
const serverUrl = configService.get<number>('app.serverUrl');
const port = configService.get<number>('app.port');
await app.listen(port);
Logger.log(`🚀 Server is running on: ${serverUrl}/${globalPrefix}`);
Logger.log(`🚀 Server is up and running!`);
};
bootstrap();

View File

@ -3,7 +3,7 @@ import { ConfigService } from '@nestjs/config';
import { SchedulerRegistry } from '@nestjs/schedule';
import { mkdir, unlink, writeFile } from 'fs/promises';
import { nanoid } from 'nanoid';
import { join, resolve } from 'path';
import { join } from 'path';
import { PDFDocument } from 'pdf-lib';
import { Browser, chromium } from 'playwright-chromium';
@ -28,7 +28,6 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
async printAsPdf(username: string, slug: string): Promise<string> {
const url = this.configService.get<string>('app.url');
const secretKey = this.configService.get<string>('app.secretKey');
const serverUrl = this.configService.get<string>('app.serverUrl');
const page = await this.browser.newPage();
@ -44,9 +43,9 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
});
const pdf = await PDFDocument.create();
const directory = resolve('dist/assets/resumes');
const directory = join(__dirname, '..', 'assets/exports');
const filename = `RxResume_PDFExport_${nanoid()}.pdf`;
const publicUrl = `${serverUrl}/resumes/${filename}`;
const publicUrl = `/api/exports/${filename}`;
for (let index = 0; index < resumePages.length; index++) {
await page.evaluate((page) => (document.body.innerHTML = page.innerHTML), resumePages[index]);

View File

@ -22,8 +22,8 @@ import { ResumeService } from './resume.service';
storage: diskStorage({
destination: async (req, _, cb) => {
const userId = (req.user as User).id;
const resumeId = req.params.id;
const destination = join(__dirname, `assets/uploads/${userId}/${resumeId}`);
const resumeId = +req.params.id;
const destination = join(__dirname, '..', `assets/uploads/${userId}/${resumeId}`);
await mkdir(destination, { recursive: true });

View File

@ -218,9 +218,8 @@ export class ResumeService {
async uploadPhoto(id: number, userId: number, filename: string) {
const resume = await this.findOne(id, userId);
const serverUrl = this.configService.get<string>('app.serverUrl');
const url = `${serverUrl}/uploads/${userId}/${id}/${filename}`;
const url = `/api/uploads/${userId}/${id}/${filename}`;
const updatedResume = set(resume, 'basics.photo.url', url);
return this.resumeRepository.save<Resume>(updatedResume);
@ -228,8 +227,8 @@ export class ResumeService {
async deletePhoto(id: number, userId: number) {
const resume = await this.findOne(id, userId);
const key = new URL(resume.basics.photo.url).pathname;
const photoPath = join(__dirname, 'assets', key);
const filepath = new URL(resume.basics.photo.url).pathname;
const photoPath = join(__dirname, '..', `assets/${filepath}`);
const updatedResume = set(resume, 'basics.photo.url', '');
await unlink(photoPath);