mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-13 22:37:14 +10:00
Merge pull request #979 from AmruthPillai/feature/turbo
Implement Turbo Workspaces, add ARM64 support, fix Google OAuth etc.
This commit is contained in:
+7
-1
@@ -1,13 +1,19 @@
|
||||
# Build Artifacts
|
||||
dist
|
||||
.next
|
||||
.turbo
|
||||
|
||||
# IDEs
|
||||
.vscode
|
||||
|
||||
# Project Metadata
|
||||
.crowdin.yml
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
SECURITY.md
|
||||
CHANGELOG.md
|
||||
CODE_OF_CONDUCT.md
|
||||
|
||||
# Project Dependencies
|
||||
node_modules
|
||||
@@ -18,4 +24,4 @@ Dockerfile
|
||||
docker-compose.yml
|
||||
|
||||
# Android App
|
||||
/app
|
||||
/app
|
||||
|
||||
+12
-6
@@ -1,7 +1,11 @@
|
||||
# Turbo Cache (Optional)
|
||||
TURBO_TEAM=
|
||||
TURBO_TOKEN=
|
||||
|
||||
# Server + Client
|
||||
TZ=UTC
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
PUBLIC_SERVER_URL=http://localhost:3000/api
|
||||
PUBLIC_URL=http://client:3000
|
||||
PUBLIC_SERVER_URL=http://server:3100
|
||||
PUBLIC_GOOGLE_CLIENT_ID=
|
||||
|
||||
# Server + Database
|
||||
@@ -18,10 +22,12 @@ JWT_SECRET=
|
||||
JWT_EXPIRY_TIME=604800
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_API_KEY=
|
||||
SENDGRID_API_KEY=
|
||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
|
||||
SENDGRID_FROM_NAME=
|
||||
SENDGRID_FROM_EMAIL=
|
||||
MAIL_FROM_NAME=
|
||||
MAIL_FROM_EMAIL=
|
||||
MAIL_HOST=
|
||||
MAIL_PORT=
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
STORAGE_BUCKET=
|
||||
STORAGE_REGION=
|
||||
STORAGE_ENDPOINT=
|
||||
|
||||
+16
-16
@@ -5,27 +5,27 @@
|
||||
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort", "unused-imports"],
|
||||
"rules": {
|
||||
// Unused Imports
|
||||
"no-unused-vars": "off",
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
// TypeScript ESLint
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
// Simple Import Sort
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
// Unused Imports
|
||||
"no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "none",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help improve
|
||||
title: "[BUG] "
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Product Flavor**
|
||||
|
||||
- [ ] Managed (https://rxresu.me)
|
||||
- [ ] Self Hosted
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Screenshots**
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: <!--[e.g. iOS]-->
|
||||
- Browser <!--[e.g. chrome, safari]-->
|
||||
- Version <!--[e.g. 22]-->
|
||||
|
||||
- OS: <!--[e.g. iOS]-->
|
||||
- Browser <!--[e.g. chrome, safari]-->
|
||||
- Version <!--[e.g. 22]-->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] "
|
||||
title: '[FEATURE] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
@@ -9,9 +9,15 @@ jobs:
|
||||
name: Client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
@@ -46,9 +52,15 @@ jobs:
|
||||
name: Server
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
client:
|
||||
name: Client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- id: vars
|
||||
name: Get Short SHA
|
||||
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Build Client Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: client/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:client-latest
|
||||
amruthpillai/reactive-resume:client-${{ steps.vars.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:client-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.vars.outputs.sha_short }}
|
||||
|
||||
server:
|
||||
name: Server
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.0.0
|
||||
|
||||
- id: buildx
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
with:
|
||||
install: true
|
||||
|
||||
- id: version
|
||||
name: Get Version
|
||||
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
|
||||
|
||||
- name: Build Server Image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
file: server/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
amruthpillai/reactive-resume:server-latest
|
||||
amruthpillai/reactive-resume:server-${{ steps.vars.outputs.sha_short }}
|
||||
ghcr.io/amruthpillai/reactive-resume:server-latest
|
||||
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.vars.outputs.sha_short }}
|
||||
+4
-1
@@ -7,4 +7,7 @@
|
||||
node_modules
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# Turbo
|
||||
.turbo
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm install
|
||||
pnpm run lint
|
||||
pnpm run format
|
||||
@@ -18,6 +18,7 @@ CHANGELOG.md
|
||||
|
||||
# Project Dependencies
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
|
||||
Vendored
+6
-2
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "lokalise.i18n-ally"]
|
||||
}
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"lokalise.i18n-ally"
|
||||
]
|
||||
}
|
||||
Vendored
+6
-6
@@ -3,12 +3,12 @@
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Debug: Server",
|
||||
"port": 9229,
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"stopOnEntry": false,
|
||||
"protocol": "inspector"
|
||||
"protocol": "inspector",
|
||||
"stopOnEntry": false
|
||||
},
|
||||
{
|
||||
"name": "Debug: Client",
|
||||
@@ -17,10 +17,10 @@
|
||||
"command": "pnpm run dev:client",
|
||||
"console": "integratedTerminal",
|
||||
"serverReadyAction": {
|
||||
"action": "debugWithChrome",
|
||||
"pattern": "started server on .+, url: (https?://.+)",
|
||||
"uriFormat": "%s",
|
||||
"action": "debugWithChrome"
|
||||
"uriFormat": "%s"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+5
-14
@@ -1,27 +1,18 @@
|
||||
{
|
||||
"css.validate": false,
|
||||
"scss.validate": false,
|
||||
"editor.wordWrap": "on",
|
||||
"npm.packageManager": "pnpm",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.wordWrap": "on",
|
||||
"eslint.workingDirectories": [
|
||||
"schema",
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"react"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"client/public/locales"
|
||||
],
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"scss.validate": false,
|
||||
"conventionalCommits.scopes": [
|
||||
"client",
|
||||
"server",
|
||||
|
||||
+11
-11
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
||||
+17
-21
@@ -1,22 +1,19 @@
|
||||
FROM node:lts-alpine as dependencies
|
||||
|
||||
RUN apk add --no-cache curl g++ make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
FROM node:lts-alpine AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-*.yaml ./
|
||||
RUN apk add --no-cache g++ git curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
FROM base as dependencies
|
||||
|
||||
COPY package.json pnpm-*.yaml turbo.json ./
|
||||
COPY ./schema/package.json ./schema/package.json
|
||||
COPY ./client/package.json ./client/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
FROM node:lts-alpine as builder
|
||||
|
||||
RUN apk add --no-cache curl g++ make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
WORKDIR /app
|
||||
FROM base as builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -24,21 +21,20 @@ COPY --from=dependencies /app/node_modules ./node_modules
|
||||
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
||||
COPY --from=dependencies /app/client/node_modules ./client/node_modules
|
||||
|
||||
RUN pnpm run build:schema
|
||||
RUN pnpm run build:client
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
|
||||
FROM node:lts-alpine as production
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
|
||||
WORKDIR /app
|
||||
RUN pnpm run build --filter client
|
||||
|
||||
RUN apk add --no-cache curl \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
FROM base as production
|
||||
|
||||
COPY --from=builder /app/pnpm-*.yaml ./
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
|
||||
COPY --from=builder /app/client/package.json ./client/package.json
|
||||
|
||||
RUN pnpm install -F client --frozen-lockfile --prod
|
||||
RUN pnpm install --filter client --prod --frozen-lockfile --workspace-root
|
||||
|
||||
COPY --from=builder /app/client/.next ./client/.next
|
||||
COPY --from=builder /app/client/public ./client/public
|
||||
@@ -52,4 +48,4 @@ ENV PORT 3000
|
||||
HEALTHCHECK --interval=30s --timeout=20s --retries=3 --start-period=15s \
|
||||
CMD curl -fSs 127.0.0.1:3000 || exit 1
|
||||
|
||||
CMD [ "pnpm", "run", "start:client" ]
|
||||
CMD [ "pnpm", "run", "start", "--filter", "client" ]
|
||||
@@ -97,7 +97,7 @@ const Settings = () => {
|
||||
<List sx={{ padding: 0 }}>
|
||||
{/* Global Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.global.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@@ -155,7 +155,7 @@ const Settings = () => {
|
||||
|
||||
{/* Page Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
@@ -187,7 +187,7 @@ const Settings = () => {
|
||||
|
||||
{/* Resume Settings */}
|
||||
<>
|
||||
<ListSubheader className="rounded">
|
||||
<ListSubheader disableSticky className="rounded">
|
||||
{t<string>('builder.rightSidebar.sections.settings.resume.heading')}
|
||||
</ListSubheader>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import { createTheme, ThemeOptions } from '@mui/material/styles';
|
||||
|
||||
const theme = createTheme({
|
||||
const theme: ThemeOptions = {
|
||||
typography: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
@@ -49,7 +49,7 @@ const theme = createTheme({
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const lightTheme = createTheme({
|
||||
...theme,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Google, Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Login, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Button, IconButton, InputAdornment, TextField } from '@mui/material';
|
||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||
import Joi from 'joi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
@@ -17,8 +18,6 @@ import { ServerError } from '@/services/axios';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setModalState } from '@/store/modal/modalSlice';
|
||||
|
||||
declare const google: any;
|
||||
|
||||
type FormData = {
|
||||
identifier: string;
|
||||
password: string;
|
||||
@@ -85,28 +84,16 @@ const LoginModal: React.FC = () => {
|
||||
dispatch(setModalState({ modal: 'auth.forgot', state: { open: true } }));
|
||||
};
|
||||
|
||||
const handleLoginWithGoogle = async () => {
|
||||
console.log(process.env.PUBLIC_GOOGLE_CLIENT_ID, env('GOOGLE_CLIENT_ID'));
|
||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||
if (response.credential) {
|
||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
||||
|
||||
google.accounts.id.initialize({
|
||||
auto_select: true,
|
||||
itp_support: true,
|
||||
client_id: env('GOOGLE_CLIENT_ID'),
|
||||
callback: async (response: any) => {
|
||||
await loginWithGoogleMutation({ credential: response.credential });
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
handleClose();
|
||||
},
|
||||
});
|
||||
|
||||
google.accounts.id.prompt((notification: any) => {
|
||||
if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
||||
const reason = notification.getNotDisplayedReason() || notification.getSkippedReason();
|
||||
|
||||
toast.error(`Google returned an error while trying to sign in: ${reason}.`);
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
}
|
||||
});
|
||||
const handleLoginWithGoogleError = () => {
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
};
|
||||
|
||||
const PasswordVisibility = (): React.ReactElement => {
|
||||
@@ -130,15 +117,7 @@ const LoginModal: React.FC = () => {
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
disabled={isLoading}
|
||||
startIcon={<Google />}
|
||||
onClick={handleLoginWithGoogle}
|
||||
>
|
||||
{t<string>('modals.auth.login.actions.google')}
|
||||
</Button>
|
||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import env from '@beam-australia/react-env';
|
||||
import { joiResolver } from '@hookform/resolvers/joi';
|
||||
import { Google, HowToReg } from '@mui/icons-material';
|
||||
import { HowToReg } from '@mui/icons-material';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { CredentialResponse, GoogleLogin } from '@react-oauth/google';
|
||||
import Joi from 'joi';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
import BaseModal from '@/components/shared/BaseModal';
|
||||
@@ -14,8 +16,6 @@ import { ServerError } from '@/services/axios';
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
||||
import { setModalState } from '@/store/modal/modalSlice';
|
||||
|
||||
declare const google: any;
|
||||
|
||||
type FormData = {
|
||||
name: string;
|
||||
username: string;
|
||||
@@ -79,18 +79,16 @@ const RegisterModal: React.FC = () => {
|
||||
dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
|
||||
};
|
||||
|
||||
const handleLoginWithGoogle = async () => {
|
||||
google.accounts.id.initialize({
|
||||
client_id: env('GOOGLE_CLIENT_ID'),
|
||||
callback: async (response: any) => {
|
||||
await loginWithGoogleMutation({ credential: response.credential });
|
||||
const handleLoginWithGoogle = async (response: CredentialResponse) => {
|
||||
if (response.credential) {
|
||||
await loginWithGoogleMutation({ credential: response.credential }, { onError: handleLoginWithGoogleError });
|
||||
|
||||
handleClose();
|
||||
},
|
||||
auto_select: false,
|
||||
});
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
google.accounts.id.prompt();
|
||||
const handleLoginWithGoogleError = () => {
|
||||
toast("Please try logging in using email/password, or use another browser that supports Google's One Tap API.");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -102,15 +100,7 @@ const RegisterModal: React.FC = () => {
|
||||
footerChildren={
|
||||
<div className="flex gap-4">
|
||||
{!isEmpty(env('GOOGLE_CLIENT_ID')) && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
disabled={isLoading}
|
||||
startIcon={<Google />}
|
||||
onClick={handleLoginWithGoogle}
|
||||
>
|
||||
{t<string>('modals.auth.register.actions.google')}
|
||||
</Button>
|
||||
<GoogleLogin onSuccess={handleLoginWithGoogle} onError={handleLoginWithGoogleError} />
|
||||
)}
|
||||
|
||||
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
|
||||
|
||||
+27
-23
@@ -2,9 +2,9 @@
|
||||
"name": "@reactive-resume/client",
|
||||
"scripts": {
|
||||
"dev": "react-env --prefix PUBLIC -- next dev",
|
||||
"lint": "next lint --fix",
|
||||
"build": "next build && npm run sitemap",
|
||||
"start": "react-env --prefix PUBLIC -- next start",
|
||||
"lint": "next lint --fix",
|
||||
"sitemap": "next-sitemap --config next-sitemap.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -16,30 +16,31 @@
|
||||
"@hookform/resolvers": "2.9.7",
|
||||
"@monaco-editor/react": "^4.4.5",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/lab": "^5.0.0-alpha.94",
|
||||
"@mui/material": "^5.10.0",
|
||||
"@mui/system": "^5.10.0",
|
||||
"@mui/x-date-pickers": "5.0.0-beta.5",
|
||||
"@next/env": "^12.2.4",
|
||||
"@reduxjs/toolkit": "^1.8.4",
|
||||
"@mui/lab": "^5.0.0-alpha.95",
|
||||
"@mui/material": "^5.10.1",
|
||||
"@mui/system": "^5.10.1",
|
||||
"@mui/x-date-pickers": "5.0.0-beta.6",
|
||||
"@next/env": "^12.2.5",
|
||||
"@react-oauth/google": "^0.2.6",
|
||||
"@reduxjs/toolkit": "^1.8.5",
|
||||
"axios": "^0.27.2",
|
||||
"clsx": "^1.2.1",
|
||||
"dayjs": "^1.11.4",
|
||||
"dayjs": "^1.11.5",
|
||||
"downloadjs": "^1.4.7",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5-hex": "^4.0.0",
|
||||
"monaco-editor": "^0.34.0",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "12.2.4",
|
||||
"next-i18next": "^11.3.0",
|
||||
"react": "18.2.0",
|
||||
"next": "12.2.5",
|
||||
"next-i18next": "^12.0.0",
|
||||
"react": "18",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-colorful": "^5.6.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.34.0",
|
||||
"react-dom": "18",
|
||||
"react-hook-form": "^7.34.2",
|
||||
"react-hot-toast": "2.3.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-icons": "^4.4.0",
|
||||
@@ -49,33 +50,36 @@
|
||||
"react-zoom-pan-pinch": "^2.1.3",
|
||||
"redux": "^4.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-saga": "^1.2.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.30.7",
|
||||
"uuid": "^8.3.2",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18",
|
||||
"@types/react-dom": "18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.10",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "18.7.1",
|
||||
"@types/react": "18.0.17",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/node": "18.7.9",
|
||||
"@types/react": "18",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react-dom": "18",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/tailwindcss": "^3.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/webfontloader": "^1.6.34",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"csstype": "^3.1.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-next": "12.2.4",
|
||||
"next-sitemap": "^3.1.17",
|
||||
"eslint-config-next": "12.2.5",
|
||||
"next-sitemap": "^3.1.21",
|
||||
"postcss": "^8.4.16",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.54.4",
|
||||
"sass": "^1.54.5",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { GetServerSideProps, NextPage } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@@ -55,8 +54,6 @@ export const getServerSideProps: GetServerSideProps<Props | Promise<Props>, Quer
|
||||
};
|
||||
|
||||
const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
|
||||
const router = useRouter();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
@@ -65,12 +62,6 @@ const Printer: NextPage<Props> = ({ resume: initialData, locale }) => {
|
||||
if (initialData) dispatch(setResume(initialData));
|
||||
}, [dispatch, initialData]);
|
||||
|
||||
useEffect(() => {
|
||||
const { pathname, asPath, query } = router;
|
||||
|
||||
router.push({ pathname, query }, asPath, { locale });
|
||||
}, [router, locale]);
|
||||
|
||||
if (!resume || isEmpty(resume)) return null;
|
||||
|
||||
const layout: string[][][] = get(resume, 'metadata.layout', []);
|
||||
|
||||
+18
-17
@@ -1,10 +1,11 @@
|
||||
import '@/styles/globals.scss';
|
||||
|
||||
import env from '@beam-australia/react-env';
|
||||
import DayjsAdapter from '@date-io/dayjs';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
import type { AppProps } from 'next/app';
|
||||
import Head from 'next/head';
|
||||
import Script from 'next/script';
|
||||
import { appWithTranslation } from 'next-i18next';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
@@ -34,27 +35,27 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
<ReduxProvider store={store}>
|
||||
<LocalizationProvider dateAdapter={DayjsAdapter}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WrapperRegistry>
|
||||
<Loading />
|
||||
<GoogleOAuthProvider clientId={env('GOOGLE_CLIENT_ID')}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<WrapperRegistry>
|
||||
<Loading />
|
||||
|
||||
<Component {...pageProps} />
|
||||
<Component {...pageProps} />
|
||||
|
||||
<ModalWrapper />
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 4000,
|
||||
className: 'toast',
|
||||
}}
|
||||
/>
|
||||
</WrapperRegistry>
|
||||
</QueryClientProvider>
|
||||
<ModalWrapper />
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
duration: 4000,
|
||||
className: 'toast',
|
||||
}}
|
||||
/>
|
||||
</WrapperRegistry>
|
||||
</QueryClientProvider>
|
||||
</GoogleOAuthProvider>
|
||||
</PersistGate>
|
||||
</LocalizationProvider>
|
||||
</ReduxProvider>
|
||||
|
||||
<Script src="https://accounts.google.com/gsi/client" async defer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
},
|
||||
"login": {
|
||||
"actions": {
|
||||
"login": "Login",
|
||||
"google": "Login with Google"
|
||||
"login": "Login"
|
||||
},
|
||||
"body": "Please enter your username and password associated with your account to login and access, manage and share your resumes.",
|
||||
"form": {
|
||||
|
||||
+21
-21
@@ -1,39 +1,39 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": [".next"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"target": "es5",
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": "./",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"@/components/*": ["components/*"],
|
||||
"@/config/*": ["config/*"],
|
||||
"@/constants/*": ["constants/*"],
|
||||
"@/data/*": ["data/*"],
|
||||
"@/i18n/*": ["i18n/*"],
|
||||
"@/modals/*": ["modals/*"],
|
||||
"@/pages/*": ["pages/*"],
|
||||
"@/public/*": ["public/*"],
|
||||
"@/services/*": ["services/*"],
|
||||
"@/store/*": ["store/*"],
|
||||
"@/styles/*": ["styles/*"],
|
||||
"@/templates/*": ["templates/*"],
|
||||
"@/types/*": ["types/*"],
|
||||
"@/utils/*": ["utils/*"],
|
||||
"@/wrappers/*": ["wrappers/*"]
|
||||
"@/config/*": ["config/*"],
|
||||
"@/modals/*": ["modals/*"],
|
||||
"@/public/*": ["public/*"],
|
||||
"@/styles/*": ["styles/*"],
|
||||
"@/services/*": ["services/*"],
|
||||
"@/wrappers/*": ["wrappers/*"],
|
||||
"@/constants/*": ["constants/*"],
|
||||
"@/templates/*": ["templates/*"],
|
||||
"@/components/*": ["components/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": [".next"]
|
||||
}
|
||||
}
|
||||
|
||||
+14
-47
@@ -1,15 +1,12 @@
|
||||
version: '3'
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14.2-alpine
|
||||
image: postgres:alpine
|
||||
container_name: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
env_file: .env.docker
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -21,7 +18,7 @@ services:
|
||||
restart: always
|
||||
|
||||
traefik:
|
||||
image: traefik:rocamadour
|
||||
image: traefik:latest
|
||||
container_name: traefik
|
||||
command:
|
||||
- --providers.docker=true
|
||||
@@ -33,37 +30,12 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
server:
|
||||
image: amruthpillai/reactive-resume:server-latest
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ./server/Dockerfile
|
||||
# image: amruthpillai/reactive-resume:server-latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./server/Dockerfile
|
||||
container_name: server
|
||||
environment:
|
||||
- TZ=UTC
|
||||
- PUBLIC_URL=http://localhost:3000
|
||||
- PUBLIC_SERVER_URL=http://localhost:3000/api
|
||||
- PUBLIC_GOOGLE_CLIENT_ID=
|
||||
- POSTGRES_DB=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- SECRET_KEY=
|
||||
- POSTGRES_HOST=postgres
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_SSL_CERT=
|
||||
- JWT_SECRET=
|
||||
- JWT_EXPIRY_TIME=604800
|
||||
- GOOGLE_CLIENT_SECRET=
|
||||
- GOOGLE_API_KEY=
|
||||
- SENDGRID_API_KEY=
|
||||
- SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
|
||||
- SENDGRID_FROM_NAME=
|
||||
- SENDGRID_FROM_EMAIL=
|
||||
- STORAGE_BUCKET=
|
||||
- STORAGE_REGION=
|
||||
- STORAGE_ENDPOINT=
|
||||
- STORAGE_URL_PREFIX=
|
||||
- STORAGE_ACCESS_KEY=
|
||||
- STORAGE_SECRET_KEY=
|
||||
env_file: .env.docker
|
||||
depends_on:
|
||||
- traefik
|
||||
- postgres
|
||||
@@ -77,17 +49,12 @@ services:
|
||||
restart: always
|
||||
|
||||
client:
|
||||
image: amruthpillai/reactive-resume:client-latest
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ./client/Dockerfile
|
||||
# image: amruthpillai/reactive-resume:client-latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./client/Dockerfile
|
||||
container_name: client
|
||||
environment:
|
||||
- TZ=UTC
|
||||
- PUBLIC_URL=http://localhost:3000
|
||||
- PUBLIC_SERVER_URL=http://localhost:3000/api
|
||||
- PUBLIC_GOOGLE_CLIENT_ID=
|
||||
- PUBLIC_FLAG_DISABLE_SIGNUPS=false
|
||||
env_file: .env.docker
|
||||
depends_on:
|
||||
- traefik
|
||||
- server
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
|
||||
@@ -136,30 +136,16 @@ You can get your own key here: https://developers.google.com/fonts/docs/develope
|
||||
|
||||
If you do not have a Google API Key, it was make use of the cached response JSON that's stored within the project source. Please note that this cache is not updated and may not have all the latest fonts that Google Fonts has to offer.
|
||||
|
||||
## SendGrid
|
||||
## Mail
|
||||
|
||||
The server makes use of SendGrid to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
||||
The server makes use of SMTP to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
|
||||
|
||||
### `SENDGRID_API_KEY`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** SendGrid API Key
|
||||
|
||||
Does not require any payment or credit card information to obtain an API key.
|
||||
|
||||
You can get your own key here: https://docs.sendgrid.com/ui/account-and-settings/api-keys
|
||||
|
||||
### `SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** Dynamic Template ID for Forgot Password
|
||||
|
||||
### `SENDGRID_FROM_NAME`
|
||||
### `MAIL_FROM_NAME`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** Sender's Name
|
||||
|
||||
### `SENDGRID_FROM_EMAIL`
|
||||
### `MAIL_FROM_EMAIL`
|
||||
|
||||
**Required**: `no`
|
||||
**Description:** Sender's Email Address
|
||||
|
||||
@@ -4,7 +4,6 @@ sidebar_position: 2
|
||||
|
||||
# Create a new resume
|
||||
|
||||
|
||||
To create a new resume from scratch, click on the selected part.
|
||||
|
||||
<a href="https://rxresu.me/dashboard">
|
||||
@@ -12,13 +11,27 @@ To create a new resume from scratch, click on the selected part.
|
||||
</a>
|
||||
|
||||
<p>
|
||||
After you click on it, enter name and select whether you want it publicly accessible
|
||||
<img align="center" src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884469/on-button_a5kfbz.png" height='25' type="image"></img> or not
|
||||
<img align="center" src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884949/off-button_rrkz3g.png" height='25' type="image"></img> . You can also change it later from the sharing <a href="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655889311/toggle_jcmfix.gif">see where</a>.
|
||||
After you click on it, enter name and select whether you want it publicly accessible
|
||||
<img
|
||||
align="center"
|
||||
src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884469/on-button_a5kfbz.png"
|
||||
height="25"
|
||||
type="image"
|
||||
></img> or not
|
||||
<img
|
||||
align="center"
|
||||
src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655884949/off-button_rrkz3g.png"
|
||||
height="25"
|
||||
type="image"
|
||||
></img> . You can also change it later from the sharing{' '}
|
||||
<a href="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655889311/toggle_jcmfix.gif">see where</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<h3> Now it's look like ⚡</h3>
|
||||
|
||||
<a href="https://rxresu.me/dashboard">
|
||||
<img src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655882355/Screenshot_2022-06-22_at_12.48.42_PM_dx6714.png" type="image"></img>
|
||||
<img
|
||||
src="https://res.cloudinary.com/dn2mupqo0/image/upload/v1655882355/Screenshot_2022-06-22_at_12.48.42_PM_dx6714.png"
|
||||
type="image"
|
||||
></img>
|
||||
</a>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'Reactive Resume',
|
||||
tagline: 'A free and open source resume builder.',
|
||||
@@ -24,7 +20,6 @@ const config = {
|
||||
blog: false,
|
||||
docs: {
|
||||
routeBasePath: '/',
|
||||
sidebarPath: require.resolve('./sidebars.js'),
|
||||
editUrl: 'https://github.com/AmruthPillai/Reactive-Resume/tree/main/docs',
|
||||
},
|
||||
sitemap: {
|
||||
|
||||
+9
-17
@@ -3,25 +3,17 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start --no-open --port 3200",
|
||||
"dev": "docusaurus start --no-open --port 3200",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve --port 3200",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
"start": "docusaurus serve --no-open --port 3200"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "^4.14.2",
|
||||
"@docusaurus/core": "2.0.1",
|
||||
"@docusaurus/preset-classic": "2.0.1",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@docusaurus/core": "2.0.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"@docusaurus/preset-classic": "2.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -36,7 +28,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.2",
|
||||
"typescript": "^4.7.4"
|
||||
"typescript": "^4.7.4",
|
||||
"@types/react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['hello'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
Generated
-49834
File diff suppressed because it is too large
Load Diff
+19
-38
@@ -1,55 +1,36 @@
|
||||
{
|
||||
"name": "reactive-resume",
|
||||
"version": "3.5.3",
|
||||
"version": "3.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "env-cmd --silent turbo run dev",
|
||||
"lint": "eslint --fix .",
|
||||
"build": "env-cmd --silent turbo run build",
|
||||
"start": "env-cmd --silent turbo run start",
|
||||
"format": "prettier --write .",
|
||||
"release": "standard-version --release-as patch"
|
||||
},
|
||||
"workspaces": [
|
||||
"schema",
|
||||
"client",
|
||||
"server",
|
||||
"docs"
|
||||
],
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"release": "standard-version",
|
||||
"lint": "eslint --quiet --fix --ext .js,.ts,.tsx .",
|
||||
"format": "prettier --loglevel error --write \"./**/*.{js,ts,tsx,json}\"",
|
||||
"dev:schema": "pnpm -F schema dev",
|
||||
"dev:server": "pnpm -F server start:debug",
|
||||
"dev:client": "pnpm -F client dev",
|
||||
"dev:docs": "pnpm -F docs start",
|
||||
"dev": "env-cmd --silent concurrently --kill-others \"pnpm run dev:*\"",
|
||||
"build:schema": "pnpm -F schema build",
|
||||
"build:server": "pnpm -F server build",
|
||||
"build:client": "pnpm -F client build",
|
||||
"build:docs": "pnpm -F docs build",
|
||||
"build": "env-cmd --silent concurrently \"pnpm run build:*\"",
|
||||
"start:server": "pnpm -F server start:prod",
|
||||
"start:client": "pnpm -F client start",
|
||||
"start:docs": "pnpm -F docs serve",
|
||||
"start": "env-cmd --silent concurrently --kill-others \"pnpm run start:*\""
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^7.3.0",
|
||||
"turbo": "^1.4.3",
|
||||
"env-cmd": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"eslint": "^8.22.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^4.7.4"
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+681
-998
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -4,12 +4,11 @@
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc -w",
|
||||
"lint": "eslint --fix --ext .ts ./src"
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.21.0",
|
||||
"eslint": "^8.22.0",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
+22
-19
@@ -1,22 +1,19 @@
|
||||
FROM node:lts-alpine as dependencies
|
||||
|
||||
RUN apk add --no-cache g++ curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
FROM node:lts-alpine AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-*.yaml ./
|
||||
RUN apk add --no-cache g++ git curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
FROM base AS dependencies
|
||||
|
||||
COPY package.json pnpm-*.yaml turbo.json ./
|
||||
COPY ./schema/package.json ./schema/package.json
|
||||
COPY ./server/package.json ./server/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
FROM node:lts-alpine as builder
|
||||
|
||||
RUN apk add --no-cache g++ curl make python3 \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
WORKDIR /app
|
||||
FROM base AS builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -24,25 +21,31 @@ COPY --from=dependencies /app/node_modules ./node_modules
|
||||
COPY --from=dependencies /app/schema/node_modules ./schema/node_modules
|
||||
COPY --from=dependencies /app/server/node_modules ./server/node_modules
|
||||
|
||||
RUN pnpm run build:schema
|
||||
RUN pnpm run build:server
|
||||
ARG TURBO_TEAM
|
||||
ARG TURBO_TOKEN
|
||||
|
||||
FROM mcr.microsoft.com/playwright:focal as production
|
||||
ENV TURBO_TEAM $TURBO_TEAM
|
||||
ENV TURBO_TOKEN $TURBO_TOKEN
|
||||
|
||||
RUN pnpm run build --filter server
|
||||
|
||||
FROM mcr.microsoft.com/playwright:next-jammy as production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y curl \
|
||||
&& apt-get install -y curl build-essential \
|
||||
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
||||
|
||||
COPY --from=builder /app/pnpm-*.yaml ./
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/package.json /app/pnpm-*.yaml /app/turbo.json ./
|
||||
COPY --from=builder /app/server/package.json ./server/package.json
|
||||
|
||||
RUN pnpm install -F server --frozen-lockfile --prod
|
||||
RUN pnpm dlx playwright install --with-deps chromium
|
||||
RUN pnpm install --filter server --prod --frozen-lockfile --workspace-root
|
||||
|
||||
COPY --from=builder /app/server/dist ./server/dist
|
||||
|
||||
VOLUME /app/server/dist/assets/exports
|
||||
VOLUME /app/server/dist/assets/uploads
|
||||
|
||||
EXPOSE 3100
|
||||
@@ -52,4 +55,4 @@ 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:server" ]
|
||||
CMD [ "pnpm", "run", "start", "--filter", "server" ]
|
||||
+17
-18
@@ -1,43 +1,40 @@
|
||||
{
|
||||
"name": "@reactive-resume/server",
|
||||
"scripts": {
|
||||
"dev": "nest start --watch",
|
||||
"build": "rimraf dist && nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint --fix --ext .ts ./src"
|
||||
"debug": "nest start --debug --watch",
|
||||
"start": "node dist/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.145.0",
|
||||
"@aws-sdk/client-s3": "^3.154.0",
|
||||
"@nestjs/axios": "^0.1.0",
|
||||
"@nestjs/common": "^9.0.9",
|
||||
"@nestjs/common": "^9.0.11",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.0.9",
|
||||
"@nestjs/core": "^9.0.11",
|
||||
"@nestjs/jwt": "^9.0.0",
|
||||
"@nestjs/mapped-types": "^1.1.0",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.9",
|
||||
"@nestjs/platform-express": "^9.0.11",
|
||||
"@nestjs/schedule": "^2.1.0",
|
||||
"@nestjs/serve-static": "^3.0.0",
|
||||
"@nestjs/terminus": "^9.1.1",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@types/passport": "^1.0.10",
|
||||
"bcrypt": "^5.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cache-manager": "^4.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"csvtojson": "^2.0.10",
|
||||
"dayjs": "^1.11.4",
|
||||
"google-auth-library": "^8.1.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"google-auth-library": "^8.3.0",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.4",
|
||||
"nanoid": "^3.3.4",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"nodemailer": "^6.7.8",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
@@ -54,13 +51,15 @@
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
"@nestjs/schematics": "^9.0.1",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.7.1",
|
||||
"eslint": "^8.21.0",
|
||||
"@types/node": "^18.7.9",
|
||||
"@types/nodemailer": "^6.4.5",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"prettier": "^2.7.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.3.1",
|
||||
|
||||
@@ -2,8 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { randomInt } from 'crypto';
|
||||
import { compareSync, hashSync } from 'bcryptjs';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
|
||||
import { PostgresErrorCode } from '@/database/errorCodes.enum';
|
||||
@@ -24,12 +23,12 @@ export class AuthService {
|
||||
) {}
|
||||
|
||||
async register(registerDto: RegisterDto) {
|
||||
const hashedPassword = await bcrypt.hash(registerDto.password, randomInt(8, 12));
|
||||
const password = hashSync(registerDto.password);
|
||||
|
||||
try {
|
||||
const createdUser = await this.usersService.create({
|
||||
...registerDto,
|
||||
password: hashedPassword,
|
||||
password,
|
||||
provider: 'email',
|
||||
});
|
||||
|
||||
@@ -43,6 +42,17 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(password: string, hashedPassword: string) {
|
||||
const isPasswordMatching = compareSync(password, hashedPassword);
|
||||
|
||||
if (!isPasswordMatching) {
|
||||
throw new HttpException(
|
||||
'The username/email and password combination provided was incorrect.',
|
||||
HttpStatus.UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getUser(identifier: string, password: string) {
|
||||
try {
|
||||
const user = await this.usersService.findByIdentifier(identifier);
|
||||
@@ -58,27 +68,17 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPassword(password: string, hashedPassword: string) {
|
||||
const isPasswordMatching = await bcrypt.compare(password, hashedPassword);
|
||||
|
||||
if (!isPasswordMatching) {
|
||||
throw new HttpException(
|
||||
'The username/email and password combination provided was incorrect.',
|
||||
HttpStatus.UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
forgotPassword(email: string) {
|
||||
return this.usersService.generateResetToken(email);
|
||||
}
|
||||
|
||||
async resetPassword(resetPasswordDto: ResetPasswordDto) {
|
||||
const user = await this.usersService.findByResetToken(resetPasswordDto.resetToken);
|
||||
const hashedPassword = await bcrypt.hash(resetPasswordDto.password, randomInt(8, 12));
|
||||
|
||||
const password = hashSync(resetPasswordDto.password);
|
||||
|
||||
await this.usersService.update(user.id, {
|
||||
password: hashedPassword,
|
||||
password,
|
||||
resetToken: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import appConfig from './app.config';
|
||||
import authConfig from './auth.config';
|
||||
import databaseConfig from './database.config';
|
||||
import googleConfig from './google.config';
|
||||
import sendgridConfig from './sendgrid.config';
|
||||
import mailConfig from './mail.config';
|
||||
import storageConfig from './storage.config';
|
||||
|
||||
const validationSchema = Joi.object({
|
||||
@@ -36,11 +36,13 @@ const validationSchema = Joi.object({
|
||||
GOOGLE_CLIENT_SECRET: Joi.string().allow(''),
|
||||
PUBLIC_GOOGLE_CLIENT_ID: Joi.string().allow(''),
|
||||
|
||||
// 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(''),
|
||||
// Mail
|
||||
MAIL_FROM_NAME: Joi.string().allow(''),
|
||||
MAIL_FROM_EMAIL: Joi.string().allow(''),
|
||||
MAIL_HOST: Joi.string().allow(''),
|
||||
MAIL_PORT: Joi.string().allow(''),
|
||||
MAIL_USERNAME: Joi.string().allow(''),
|
||||
MAIL_PASSWORD: Joi.string().allow(''),
|
||||
|
||||
// Storage
|
||||
STORAGE_BUCKET: Joi.string().allow(''),
|
||||
@@ -54,7 +56,7 @@ const validationSchema = Joi.object({
|
||||
@Module({
|
||||
imports: [
|
||||
NestConfigModule.forRoot({
|
||||
load: [appConfig, authConfig, databaseConfig, googleConfig, sendgridConfig, storageConfig],
|
||||
load: [appConfig, authConfig, databaseConfig, googleConfig, mailConfig, storageConfig],
|
||||
validationSchema: validationSchema,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('mail', () => ({
|
||||
from: {
|
||||
name: process.env.MAIL_FROM_NAME,
|
||||
email: process.env.MAIL_FROM_EMAIL,
|
||||
},
|
||||
host: process.env.MAIL_HOST,
|
||||
port: process.env.MAIL_PORT,
|
||||
username: process.env.MAIL_USERNAME,
|
||||
password: process.env.MAIL_PASSWORD,
|
||||
}));
|
||||
@@ -1,8 +0,0 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('sendgrid', () => ({
|
||||
apiKey: process.env.SENDGRID_API_KEY,
|
||||
forgotPasswordTemplateId: process.env.SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID,
|
||||
fromName: process.env.SENDGRID_FROM_NAME,
|
||||
fromEmail: process.env.SENDGRID_FROM_EMAIL,
|
||||
}));
|
||||
@@ -12,8 +12,6 @@ export class HealthController {
|
||||
@Get()
|
||||
@HealthCheck()
|
||||
check() {
|
||||
return this.health.check([
|
||||
() => this.db.pingCheck('database'),
|
||||
]);
|
||||
return this.health.check([() => this.db.pingCheck('database')]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class MailRecipient {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class SendMailDto {
|
||||
@IsDefined()
|
||||
@Type(() => MailRecipient)
|
||||
from: MailRecipient;
|
||||
|
||||
@IsDefined()
|
||||
@Type(() => MailRecipient)
|
||||
to: MailRecipient;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
subject: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
message: string;
|
||||
}
|
||||
@@ -1,40 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import SendGrid from '@sendgrid/mail';
|
||||
import { createTransport, Transporter } from 'nodemailer';
|
||||
|
||||
import { User } from '@/users/entities/user.entity';
|
||||
|
||||
import { SendMailDto } from './dto/send-mail.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
constructor(private configService: ConfigService) {
|
||||
const sendGridApiKey = this.configService.get<string>('sendgrid.apiKey');
|
||||
transporter: Transporter;
|
||||
|
||||
if (sendGridApiKey) {
|
||||
SendGrid.setApiKey(this.configService.get<string>('sendgrid.apiKey'));
|
||||
}
|
||||
constructor(private configService: ConfigService) {
|
||||
this.transporter = createTransport({
|
||||
host: this.configService.get<string>('mail.host'),
|
||||
port: this.configService.get<number>('mail.port'),
|
||||
pool: true,
|
||||
secure: false,
|
||||
tls: { ciphers: 'SSLv3' },
|
||||
auth: {
|
||||
user: this.configService.get<string>('mail.username'),
|
||||
pass: this.configService.get<string>('mail.password'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async sendEmail(mail: SendGrid.MailDataRequired) {
|
||||
return SendGrid.send(mail);
|
||||
async sendEmail(sendMailDto: SendMailDto) {
|
||||
this.transporter.sendMail({
|
||||
from: `${sendMailDto.from.name} <${sendMailDto.from.email}>`,
|
||||
to: `${sendMailDto.to.name} <${sendMailDto.to.email}>`,
|
||||
subject: sendMailDto.subject,
|
||||
text: sendMailDto.message,
|
||||
html: sendMailDto.message,
|
||||
});
|
||||
}
|
||||
|
||||
async sendForgotPasswordEmail(user: User, resetToken: string): Promise<void> {
|
||||
const appUrl = this.configService.get<string>('app.url');
|
||||
const url = `${appUrl}?modal=auth.reset&resetToken=${resetToken}`;
|
||||
|
||||
const mailData: SendGrid.MailDataRequired = {
|
||||
const sendMailDto: SendMailDto = {
|
||||
from: {
|
||||
name: this.configService.get<string>('sendgrid.fromName'),
|
||||
email: this.configService.get<string>('sendgrid.fromEmail'),
|
||||
name: this.configService.get<string>('mail.from.name'),
|
||||
email: this.configService.get<string>('mail.from.email'),
|
||||
},
|
||||
to: user.email,
|
||||
hideWarnings: true,
|
||||
dynamicTemplateData: { url },
|
||||
templateId: this.configService.get<string>('sendgrid.forgotPasswordTemplateId'),
|
||||
to: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
},
|
||||
subject: 'Reset your Reactive Resume password',
|
||||
message: `<p>Hey ${user.name}!</p> <p>You can reset your password by visiting this link: <a href="${url}">${url}</a>.</p> <p>But hurry, because it will expire in 30 minutes.</p>`,
|
||||
};
|
||||
|
||||
await SendGrid.send(mailData);
|
||||
|
||||
return;
|
||||
await this.sendEmail(sendMailDto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Connection, Repository } from 'typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { MailService } from '@/mail/mail.service';
|
||||
|
||||
@@ -19,7 +19,7 @@ export class UsersService {
|
||||
@InjectRepository(User) private userRepository: Repository<User>,
|
||||
private schedulerRegistry: SchedulerRegistry,
|
||||
private mailService: MailService,
|
||||
private connection: Connection
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async findById(id: number): Promise<User> {
|
||||
@@ -93,7 +93,7 @@ export class UsersService {
|
||||
const user = await this.findByEmail(email);
|
||||
|
||||
const resetToken = randomBytes(32).toString('hex');
|
||||
const queryRunner = this.connection.createQueryRunner();
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
const timeout = setTimeout(async () => {
|
||||
await this.userRepository.update(user.id, { resetToken: null });
|
||||
|
||||
+15
-15
@@ -3,33 +3,33 @@
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"strictNullChecks": false,
|
||||
"resolveJsonModule": true,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"baseUrl": ".",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"paths": {
|
||||
"@/auth/*": ["src/auth/*"],
|
||||
"@/config/*": ["src/config/*"],
|
||||
"@/constants/*": ["src/constants/*"],
|
||||
"@/database/*": ["src/database/*"],
|
||||
"@/decorators/*": ["src/decorators/*"],
|
||||
"@/filters/*": ["src/filters/*"],
|
||||
"@/mail/*": ["src/mail/*"],
|
||||
"@/users/*": ["src/users/*"],
|
||||
"@/config/*": ["src/config/*"],
|
||||
"@/resume/*": ["src/resume/*"],
|
||||
"@/users/*": ["src/users/*"]
|
||||
"@/filters/*": ["src/filters/*"],
|
||||
"@/database/*": ["src/database/*"],
|
||||
"@/constants/*": ["src/constants/*"],
|
||||
"@/decorators/*": ["src/decorators/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"pipeline": {
|
||||
"dev": {
|
||||
"cache": false
|
||||
},
|
||||
"start": {
|
||||
"cache": false
|
||||
},
|
||||
"build": {
|
||||
"outputs": ["dist/**", ".next/**"],
|
||||
"dependsOn": ["^build"]
|
||||
}
|
||||
},
|
||||
"globalDependencies": [".env"]
|
||||
}
|
||||
Reference in New Issue
Block a user