updating dependencies, fixing server reloads

This commit is contained in:
Amruth Pillai
2023-06-07 18:39:14 +02:00
parent c571f201d3
commit d4b6c16bf9
25 changed files with 1674 additions and 1821 deletions

View File

@ -1,12 +1,23 @@
{
"ignorePatterns": ["/app"],
"parser": "@typescript-eslint/parser",
"extends": ["plugin:@typescript-eslint/recommended"],
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort"],
"plugins": ["@typescript-eslint/eslint-plugin", "unused-imports", "simple-import-sort"],
"rules": {
// ESLint
"no-unused-vars": "off",
// Unused Imports
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"args": "none",
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}
],
// Simple Import Sort
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",

View File

@ -49,7 +49,7 @@ jobs:
with:
context: .
push: true
platforms: linux/amd64
platforms: linux/amd64,linux/arm64
file: ${{ matrix.image }}/Dockerfile
tags: |
amruthpillai/reactive-resume:${{ matrix.image }}-latest

View File

@ -40,7 +40,4 @@ EXPOSE 3000
ENV PORT 3000
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
CMD curl -fSs localhost:3000 || exit 1
CMD [ "pnpm", "run", "start" ]

View File

@ -0,0 +1,43 @@
FROM node:18-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn global add pnpm && pnpm build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "client/server.js"]

View File

@ -58,12 +58,10 @@ const ResumeInput: React.FC<Props> = ({ type = 'text', label, path, className, m
openTo="year"
label={label}
value={dayjs(value)}
views={['year', 'month', 'day']}
slots={{
textField: (params) => <TextField {...params} error={false} className={className} />,
}}
slots={{ textField: (params) => <TextField {...params} error={false} className={className} /> }}
onChange={(date: dayjs.Dayjs | null) => {
date && dayjs(date).isValid() && onChangeValue(dayjs(date).format('YYYY-MM-DD'));
if (!date) return onChangeValue('');
if (dayjs(date).isValid()) return onChangeValue(dayjs(date).format('YYYY-MM-DD'));
}}
/>
);

View File

@ -3,6 +3,7 @@ const { i18n } = require('./next-i18next.config');
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
i18n,

View File

@ -9,74 +9,74 @@
},
"dependencies": {
"@beam-australia/react-env": "^3.1.1",
"@emotion/css": "^11.10.6",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@emotion/css": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hello-pangea/dnd": "^16.2.0",
"@hookform/resolvers": "3.1.0",
"@monaco-editor/react": "^4.5.0",
"@monaco-editor/react": "^4.5.1",
"@mui/icons-material": "^5.11.16",
"@mui/lab": "^5.0.0-alpha.127",
"@mui/material": "^5.12.1",
"@mui/system": "^5.12.1",
"@mui/x-date-pickers": "6.2.1",
"@react-oauth/google": "^0.10.0",
"@mui/lab": "^5.0.0-alpha.133",
"@mui/material": "^5.13.4",
"@mui/system": "^5.13.2",
"@mui/x-date-pickers": "6.6.0",
"@react-oauth/google": "^0.11.0",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^1.3.6",
"axios": "^1.4.0",
"clsx": "^1.2.1",
"dayjs": "^1.11.7",
"dayjs": "^1.11.8",
"downloadjs": "^1.4.7",
"joi": "^17.9.1",
"joi": "^17.9.2",
"lodash": "^4.17.21",
"md5-hex": "^4.0.0",
"monaco-editor": "^0.37.1",
"monaco-editor": "^0.39.0",
"nanoid": "3.3.4",
"next": "13.3.0",
"next-i18next": "^13.2.2",
"next": "13.4.4",
"next-i18next": "^13.3.0",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.9",
"react-hot-toast": "2.4.0",
"react-icons": "^4.8.0",
"react-hook-form": "^7.44.3",
"react-hot-toast": "2.4.1",
"react-icons": "^4.9.0",
"react-markdown": "^8.0.7",
"react-query": "^3.39.3",
"react-redux": "^8.0.5",
"react-zoom-pan-pinch": "^3.0.7",
"react-redux": "^8.0.7",
"react-zoom-pan-pinch": "^3.0.8",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.2.3",
"redux-undo": "^1.0.1",
"rehype-katex": "^6.0.2",
"rehype-katex": "^6.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sharp": "^0.32.0",
"sharp": "^0.32.1",
"uuid": "^9.0.0",
"webfontloader": "^1.6.28"
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/core": "^7.22.1",
"@reactive-resume/schema": "workspace:*",
"@tailwindcss/typography": "^0.5.9",
"@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.194",
"@types/node": "^18.15.12",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@types/lodash": "^4.14.195",
"@types/node": "^20.2.5",
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"@types/react-redux": "^7.1.25",
"@types/uuid": "^9.0.1",
"@types/webfontloader": "^1.6.35",
"autoprefixer": "^10.4.14",
"csstype": "^3.1.2",
"eslint-config-next": "^13.3.0",
"eslint-plugin-tailwindcss": "^3.11.0",
"eslint-config-next": "^13.4.4",
"eslint-plugin-tailwindcss": "^3.12.1",
"eslint-plugin-unused-imports": "^2.0.0",
"next-sitemap": "^4.0.7",
"postcss": "^8.4.23",
"sass": "^1.62.0",
"tailwindcss": "^3.3.1",
"typescript": "^5.0.4"
"next-sitemap": "^4.1.3",
"postcss": "^8.4.24",
"sass": "^1.62.1",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.3"
}
}

View File

@ -8,22 +8,16 @@ services:
- 5432:5432
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
start_period: 15s
interval: 30s
timeout: 30s
retries: 3
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
server:
image: amruthpillai/reactive-resume:server-latest
# build:
# context: .
# dockerfile: ./server/Dockerfile
# image: amruthpillai/reactive-resume:server-latest
build:
context: .
dockerfile: ./server/Dockerfile
restart: always
ports:
- 3100:3100
@ -59,10 +53,10 @@ services:
- PDF_DELETION_TIME=
client:
image: amruthpillai/reactive-resume:client-latest
# build:
# context: .
# dockerfile: ./client/Dockerfile
# image: amruthpillai/reactive-resume:client-latest
build:
context: .
dockerfile: ./client/Dockerfile
restart: always
ports:
- 3000:3000

View File

@ -23,16 +23,17 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/node": "^18.15.12",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"eslint": "^8.38.0",
"@types/node": "^20.2.5",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"eslint": "^8.42.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-simple-import-sort": "^10.0.0",
"npm-check-updates": "^16.10.8",
"prettier": "^2.8.7",
"eslint-plugin-unused-imports": "^2.0.0",
"npm-check-updates": "^16.10.12",
"prettier": "^2.8.8",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
"typescript": "^5.1.3"
},
"resolutions": {
"@types/react": "17.0.2",

3090
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
"build": "tsc"
},
"devDependencies": {
"eslint": "^8.38.0",
"typescript": "^5.0.4"
"eslint": "^8.42.0",
"typescript": "^5.1.3"
}
}

View File

@ -1,19 +1,7 @@
{
"root": true,
"extends": "../.eslintrc.json",
"plugins": ["unused-imports"],
"ignorePatterns": ["dist"],
"env": { "node": true },
"rules": {
// Unused Imports
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"args": "none",
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}
]
}
"rules": {}
}

1
server/.gitignore vendored
View File

@ -13,6 +13,7 @@ lerna-debug.log*
# OS
.DS_Store
.playwright
# Tests
/coverage

View File

@ -47,7 +47,4 @@ EXPOSE 3100
ENV PORT 3100
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
CMD curl -fSs localhost:3100/health || exit 1
CMD [ "pnpm", "run", "start" ]

View File

@ -8,68 +8,68 @@
"start": "node dist/main"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.317.0",
"@aws-sdk/client-s3": "^3.347.1",
"@nestjs/axios": "^2.0.0",
"@nestjs/cache-manager": "^1.0.0",
"@nestjs/common": "^9.4.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.4.0",
"@nestjs/common": "^9.4.2",
"@nestjs/config": "^2.3.2",
"@nestjs/core": "^9.4.2",
"@nestjs/jwt": "^10.0.3",
"@nestjs/mapped-types": "^1.2.2",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.4.0",
"@nestjs/schedule": "^2.2.1",
"@nestjs/platform-express": "^9.4.2",
"@nestjs/schedule": "^2.2.2",
"@nestjs/serve-static": "^3.0.1",
"@nestjs/terminus": "^9.2.2",
"@nestjs/typeorm": "^9.0.1",
"@types/passport": "^1.0.12",
"bcryptjs": "^2.4.3",
"cache-manager": "^5.2.0",
"cache-manager": "^5.2.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"csvtojson": "^2.0.10",
"dayjs": "^1.11.7",
"google-auth-library": "^8.7.0",
"joi": "^17.9.1",
"dayjs": "^1.11.8",
"google-auth-library": "^8.8.0",
"joi": "^17.9.2",
"lodash": "^4.17.21",
"multer": "^1.4.5-lts.1",
"nanoid": "3.3.4",
"node-stream-zip": "^1.15.0",
"nodemailer": "^6.9.1",
"nodemailer": "^6.9.3",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pdf-lib": "^1.17.1",
"pg": "^8.10.0",
"playwright-chromium": "^1.32.3",
"pg": "^8.11.0",
"playwright-chromium": "^1.34.3",
"reflect-metadata": "^0.1.13",
"rimraf": "^5.0.0",
"rxjs": "^7.8.0",
"typeorm": "0.3.15",
"rxjs": "^7.8.1",
"typeorm": "0.3.16",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.4.0",
"@nestjs/schematics": "^9.1.0",
"@nestjs/cli": "^9.5.0",
"@nestjs/schematics": "^9.2.0",
"@reactive-resume/schema": "workspace:*",
"@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.17",
"@types/lodash": "^4.14.194",
"@types/lodash": "^4.14.195",
"@types/multer": "^1.4.7",
"@types/node": "^18.15.12",
"@types/nodemailer": "^6.4.7",
"@types/node": "^20.2.5",
"@types/nodemailer": "^6.4.8",
"@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.35",
"@types/uuid": "^9.0.1",
"eslint-plugin-unused-imports": "^2.0.0",
"prettier": "^2.8.7",
"prettier": "^2.8.8",
"rimraf": "^5.0.1",
"source-map-support": "^0.5.21",
"ts-loader": "^9.4.2",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.0.4",
"webpack": "^5.80.0"
"typescript": "^5.1.3",
"webpack": "^5.86.0"
}
}

View File

@ -7,9 +7,8 @@ import { join } from 'path';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from './config/config.module';
import { DatabaseModule } from './database/database.module';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { AllExceptionsFilter } from './filters/all-exceptions.filter';
import { FontsModule } from './fonts/fonts.module';
import { HealthModule } from './health/health.module';
import { IntegrationsModule } from './integrations/integrations.module';
import { MailModule } from './mail/mail.module';
import { PrinterModule } from './printer/printer.module';
@ -33,7 +32,6 @@ import { UsersModule } from './users/users.module';
FontsModule,
IntegrationsModule,
PrinterModule,
HealthModule,
],
providers: [
{
@ -42,7 +40,7 @@ import { UsersModule } from './users/users.module';
},
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
useClass: AllExceptionsFilter,
},
],
})

View File

@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, HttpCode, Patch, Post, UseGuards } from '@nestjs/common';
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, Patch, Post, UseGuards } from '@nestjs/common';
import { User } from '@/decorators/user.decorator';
import { User as UserEntity } from '@/users/entities/user.entity';
@ -23,10 +23,14 @@ export class AuthController {
@Post('google')
async loginWithGoogle(@Body('credential') credential: string) {
const user = await this.authService.authenticateWithGoogle(credential);
const accessToken = this.authService.getAccessToken(user.id);
try {
const user = await this.authService.authenticateWithGoogle(credential);
const accessToken = this.authService.getAccessToken(user.id);
return { user, accessToken };
return { user, accessToken };
} catch (error) {
throw new BadRequestException('User with this email might already exist.');
}
}
@Post('register')

View File

@ -0,0 +1,23 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}

View File

@ -1,22 +0,0 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
import type { Request, Response } from 'express';
import { TypeORMError } from 'typeorm';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const statusCode = exception.getStatus();
const message = (exception.getResponse() as TypeORMError).message || exception.message;
response.status(statusCode).json({
statusCode,
message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}

View File

@ -1,4 +1,5 @@
import { CacheInterceptor, Controller, Get, UseGuards, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';
import { Controller, Get, UseGuards, UseInterceptors } from '@nestjs/common';
import { JwtAuthGuard } from '@/auth/guards/jwt.guard';

View File

@ -1,13 +0,0 @@
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(private health: HealthCheckService, private db: TypeOrmHealthIndicator) {}
@Get()
@HealthCheck()
check() {
return this.health.check([() => this.db.pingCheck('database')]);
}
}

View File

@ -1,11 +0,0 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
@Module({
imports: [HttpModule, TerminusModule],
controllers: [HealthController],
})
export class HealthModule {}

View File

@ -1,4 +1,4 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { Controller, GatewayTimeoutException,Get, Param, Query } from '@nestjs/common';
import { PrinterService } from './printer.service';
@ -7,11 +7,15 @@ export class PrinterController {
constructor(private readonly printerService: PrinterService) {}
@Get('/:username/:slug')
printAsPdf(
async printAsPdf(
@Param('username') username: string,
@Param('slug') slug: string,
@Query('lastUpdated') lastUpdated: string
): Promise<string> {
return this.printerService.printAsPdf(username, slug, lastUpdated);
try {
return await this.printerService.printAsPdf(username, slug, lastUpdated);
} catch (error) {
throw new GatewayTimeoutException();
}
}
}

View File

@ -5,17 +5,57 @@ import { PageConfig } from '@reactive-resume/schema';
import { access, mkdir, readdir, unlink, writeFile } from 'fs/promises';
import { join } from 'path';
import { PDFDocument } from 'pdf-lib';
import { Browser, chromium } from 'playwright-chromium';
import { BrowserContext, chromium } from 'playwright-chromium';
const minimal_chromium_args = [
'--autoplay-policy=user-gesture-required',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-update',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-domain-reliability',
'--disable-extensions',
'--disable-features=AudioServiceOutOfProcess',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-notifications',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-popup-blocking',
'--disable-print-preview',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-setuid-sandbox',
'--disable-speech-api',
'--disable-sync',
'--hide-scrollbars',
'--ignore-gpu-blacklist',
'--metrics-recording-only',
'--mute-audio',
'--no-default-browser-check',
'--no-first-run',
'--no-pings',
'--no-sandbox',
'--no-zygote',
'--password-store=basic',
'--use-gl=swiftshader',
'--use-mock-keychain',
];
@Injectable()
export class PrinterService implements OnModuleInit, OnModuleDestroy {
private browser: Browser;
private browser: BrowserContext;
constructor(private readonly schedulerRegistry: SchedulerRegistry, private readonly configService: ConfigService) {}
async onModuleInit() {
this.browser = await chromium.launch({
args: ['--disable-dev-shm-usage'],
this.browser = await chromium.launchPersistentContext('.playwright', {
headless: true,
forcedColors: 'active',
args: minimal_chromium_args,
});
}
@ -34,6 +74,7 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
await access(join(directory, filename));
} catch {
const activeSchedulerTimeouts = this.schedulerRegistry.getTimeouts();
await readdir(directory).then(async (files) => {
await Promise.all(
files.map(async (file) => {
@ -53,9 +94,8 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
const page = await this.browser.newPage();
await page.goto(`${url}/${username}/${slug}/printer?secretKey=${secretKey}`);
await page.waitForLoadState('networkidle');
await page.waitForSelector('html.wf-active');
await page.goto(`${url}/${username}/${slug}/printer?secretKey=${secretKey}`, { waitUntil: 'networkidle' });
await page.waitForSelector('html.wf-active', { state: 'visible' });
const pageFormat: PageConfig['format'] = await page.$$eval(
'[data-page]',