feat(health): add health checks to server api

This commit is contained in:
Amruth Pillai
2022-03-10 09:25:15 +01:00
parent 8f48f5fcd6
commit eca80a1663
12 changed files with 114 additions and 26 deletions

View File

@ -41,7 +41,7 @@ const Profiles = () => {
<footer className="flex justify-end"> <footer className="flex justify-end">
<Button variant="outlined" startIcon={<Add />} onClick={handleAdd}> <Button variant="outlined" startIcon={<Add />} onClick={handleAdd}>
{t('builder.common.actions.add', { {t('builder.common.actions.add', {
section: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }), token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
})} })}
</Button> </Button>
</footer> </footer>

View File

@ -122,7 +122,7 @@ const LoginModal: React.FC = () => {
startIcon={<Google />} startIcon={<Google />}
onClick={handleLoginWithGoogle} onClick={handleLoginWithGoogle}
> >
{t('modals.auth.login.actions.login-google')} {t('modals.auth.login.actions.google')}
</Button> </Button>
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}> <Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>

View File

@ -1,13 +1,15 @@
import env from '@beam-australia/react-env';
import { joiResolver } from '@hookform/resolvers/joi'; import { joiResolver } from '@hookform/resolvers/joi';
import { HowToReg } from '@mui/icons-material'; import { Google, HowToReg } from '@mui/icons-material';
import { Button, TextField } from '@mui/material'; import { Button, TextField } from '@mui/material';
import Joi from 'joi'; import Joi from 'joi';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { GoogleLoginResponse, GoogleLoginResponseOffline, useGoogleLogin } from 'react-google-login';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import { register as registerUser, RegisterParams } from '@/services/auth'; import { loginWithGoogle, LoginWithGoogleParams, register as registerUser, RegisterParams } from '@/services/auth';
import { ServerError } from '@/services/axios'; import { ServerError } from '@/services/axios';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
@ -56,6 +58,19 @@ const RegisterModal: React.FC = () => {
const { mutateAsync, isLoading } = useMutation<void, ServerError, RegisterParams>(registerUser); const { mutateAsync, isLoading } = useMutation<void, ServerError, RegisterParams>(registerUser);
const { mutateAsync: loginWithGoogleMutation } = useMutation<void, ServerError, LoginWithGoogleParams>(
loginWithGoogle
);
const { signIn } = useGoogleLogin({
clientId: env('GOOGLE_CLIENT_ID'),
onSuccess: async (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
await loginWithGoogleMutation({ accessToken: (response as GoogleLoginResponse).accessToken });
handleClose();
},
});
const handleClose = () => { const handleClose = () => {
dispatch(setModalState({ modal: 'auth.register', state: { open: false } })); dispatch(setModalState({ modal: 'auth.register', state: { open: false } }));
reset(); reset();
@ -63,7 +78,6 @@ const RegisterModal: React.FC = () => {
const onSubmit = async ({ name, username, email, password }: FormData) => { const onSubmit = async ({ name, username, email, password }: FormData) => {
await mutateAsync({ name, username, email, password }); await mutateAsync({ name, username, email, password });
handleClose(); handleClose();
}; };
@ -72,6 +86,10 @@ const RegisterModal: React.FC = () => {
dispatch(setModalState({ modal: 'auth.login', state: { open: true } })); dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
}; };
const handleLoginWithGoogle = () => {
signIn();
};
return ( return (
<BaseModal <BaseModal
icon={<HowToReg />} icon={<HowToReg />}
@ -79,9 +97,21 @@ const RegisterModal: React.FC = () => {
heading={t('modals.auth.register.heading')} heading={t('modals.auth.register.heading')}
handleClose={handleClose} handleClose={handleClose}
footerChildren={ footerChildren={
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}> <>
{t('modals.auth.register.actions.register')} <Button
</Button> type="submit"
variant="outlined"
disabled={isLoading}
startIcon={<Google />}
onClick={handleLoginWithGoogle}
>
{t('modals.auth.register.actions.google')}
</Button>
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
{t('modals.auth.register.actions.register')}
</Button>
</>
} }
> >
<p>{t('modals.auth.register.body')}</p> <p>{t('modals.auth.register.body')}</p>

View File

@ -45,10 +45,10 @@ const ProfileModal: React.FC = () => {
const isEditMode = useMemo(() => !!item, [item]); const isEditMode = useMemo(() => !!item, [item]);
const addText = t('builder.common.actions.add', { const addText = t('builder.common.actions.add', {
section: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }), token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
}); });
const editText = t('builder.common.actions.edit', { const editText = t('builder.common.actions.edit', {
section: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }), token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
}); });
const { reset, control, handleSubmit } = useForm<FormData>({ const { reset, control, handleSubmit } = useForm<FormData>({

View File

@ -147,18 +147,6 @@ const Home: NextPage = () => {
<h6>{t('landing.links.heading')}</h6> <h6>{t('landing.links.heading')}</h6>
<div> <div>
<a href={GITHUB_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.github')}
</Button>
</a>
<a href={DONATION_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.donate')}
</Button>
</a>
<Link href="/meta/privacy" passHref> <Link href="/meta/privacy" passHref>
<Button variant="text" startIcon={<LinkIcon />}> <Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.privacy')} {t('landing.links.links.privacy')}
@ -170,6 +158,18 @@ const Home: NextPage = () => {
{t('landing.links.links.service')} {t('landing.links.links.service')}
</Button> </Button>
</Link> </Link>
<a href={GITHUB_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.github')}
</Button>
</a>
<a href={DONATION_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.donate')}
</Button>
</a>
</div> </div>
</section> </section>

View File

@ -16,7 +16,7 @@
"login": { "login": {
"actions": { "actions": {
"login": "Login", "login": "Login",
"login-google": "Login with Google" "google": "Login with Google"
}, },
"body": "Please enter your username and password associated with your account to login and access, manage and share your resumes.", "body": "Please enter your username and password associated with your account to login and access, manage and share your resumes.",
"form": { "form": {
@ -34,7 +34,8 @@
}, },
"register": { "register": {
"actions": { "actions": {
"register": "Register" "register": "Register",
"google": "Register with Google"
}, },
"body": "Please enter your personal information to create an account.", "body": "Please enter your personal information to create an account.",
"form": { "form": {
@ -112,7 +113,7 @@
"actions": { "actions": {
"upload-json": "Upload JSON" "upload-json": "Upload JSON"
}, },
"body": "If you have a JSON that was exported with the current version of Reactive Resume, you can import it back here to get an editable version again.", "body": "If you have a JSON that was exported with the current version of Reactive Resume, you can import it back here to get an editable version again. Previous versions of Reactive Resume are unfortunately not supported at the moment.",
"heading": "Import From Reactive Resume" "heading": "Import From Reactive Resume"
} }
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "reactive-resume", "name": "reactive-resume",
"version": "3.0.0-beta.2", "version": "3.0.0-beta.4",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"schema", "schema",

22
pnpm-lock.yaml generated
View File

@ -188,6 +188,7 @@ importers:
'@nestjs/schedule': ^1.0.2 '@nestjs/schedule': ^1.0.2
'@nestjs/schematics': ^8.0.8 '@nestjs/schematics': ^8.0.8
'@nestjs/serve-static': ^2.2.2 '@nestjs/serve-static': ^2.2.2
'@nestjs/terminus': ^8.0.4
'@nestjs/typeorm': ^8.0.3 '@nestjs/typeorm': ^8.0.3
'@reactive-resume/schema': workspace:* '@reactive-resume/schema': workspace:*
'@sendgrid/mail': ^7.6.1 '@sendgrid/mail': ^7.6.1
@ -240,6 +241,7 @@ importers:
'@nestjs/platform-express': 8.4.0_31e7036b193d6d3c9cadab18cbb4af84 '@nestjs/platform-express': 8.4.0_31e7036b193d6d3c9cadab18cbb4af84
'@nestjs/schedule': 1.0.2_1ce925e2290a1cea9e3700e8a60baeb5 '@nestjs/schedule': 1.0.2_1ce925e2290a1cea9e3700e8a60baeb5
'@nestjs/serve-static': 2.2.2_31e7036b193d6d3c9cadab18cbb4af84 '@nestjs/serve-static': 2.2.2_31e7036b193d6d3c9cadab18cbb4af84
'@nestjs/terminus': 8.0.4_44ad68f90df6df0ad3d3ea7593df94f3
'@nestjs/typeorm': 8.0.3_d17aee4fbe284d59b832be708c000fe0 '@nestjs/typeorm': 8.0.3_d17aee4fbe284d59b832be708c000fe0
'@sendgrid/mail': 7.6.1 '@sendgrid/mail': 7.6.1
'@types/passport': 1.0.7 '@types/passport': 1.0.7
@ -1413,6 +1415,21 @@ packages:
path-to-regexp: 0.1.7 path-to-regexp: 0.1.7
dev: false dev: false
/@nestjs/terminus/8.0.4_44ad68f90df6df0ad3d3ea7593df94f3:
resolution: {integrity: sha512-KjeY7VLt0Az6pA2wO67nkL1QbE68yBb+FLZ7+aa+C/g/IKoDR668nqSuFzJarBrnFBTGEwDD09BwsgqkmymrbQ==}
peerDependencies:
'@nestjs/common': 8.x
'@nestjs/core': 8.x
reflect-metadata: 0.1.x
rxjs: 7.x
dependencies:
'@nestjs/common': 8.4.0_add13df2cdecb4b62cd3f7664ea82e18
'@nestjs/core': 8.4.0_ded80713f50ec1c99e9ba95af1765d72
check-disk-space: 3.1.0
reflect-metadata: 0.1.13
rxjs: 7.5.5
dev: false
/@nestjs/typeorm/8.0.3_d17aee4fbe284d59b832be708c000fe0: /@nestjs/typeorm/8.0.3_d17aee4fbe284d59b832be708c000fe0:
resolution: {integrity: sha512-tf9rTXP6LeFInkwd+tktQhtLRsKp4RRYImprqT8gcHcJDx+xMP1IygnXELOKwF5vo2/mnhrGtBlRQ/iiS6170g==} resolution: {integrity: sha512-tf9rTXP6LeFInkwd+tktQhtLRsKp4RRYImprqT8gcHcJDx+xMP1IygnXELOKwF5vo2/mnhrGtBlRQ/iiS6170g==}
peerDependencies: peerDependencies:
@ -2808,6 +2825,11 @@ packages:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
dev: true dev: true
/check-disk-space/3.1.0:
resolution: {integrity: sha512-4L3WVw4uPaBJocwnxTCWTTHc8mNu080pjCVBhZeWFdnaQBAremLHpJ1H90G+uEA0rJcW43fghYMsLBXID9X4Zg==}
engines: {node: '>=12'}
dev: false
/chokidar/3.5.3: /chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}

View File

@ -20,6 +20,7 @@
"@nestjs/platform-express": "^8.4.0", "@nestjs/platform-express": "^8.4.0",
"@nestjs/schedule": "^1.0.2", "@nestjs/schedule": "^1.0.2",
"@nestjs/serve-static": "^2.2.2", "@nestjs/serve-static": "^2.2.2",
"@nestjs/terminus": "^8.0.4",
"@nestjs/typeorm": "^8.0.3", "@nestjs/typeorm": "^8.0.3",
"@sendgrid/mail": "^7.6.1", "@sendgrid/mail": "^7.6.1",
"@types/passport": "^1.0.7", "@types/passport": "^1.0.7",

View File

@ -9,6 +9,7 @@ import { ConfigModule } from './config/config.module';
import { DatabaseModule } from './database/database.module'; import { DatabaseModule } from './database/database.module';
import { HttpExceptionFilter } from './filters/http-exception.filter'; import { HttpExceptionFilter } from './filters/http-exception.filter';
import { FontsModule } from './fonts/fonts.module'; import { FontsModule } from './fonts/fonts.module';
import { HealthModule } from './health/health.module';
import { IntegrationsModule } from './integrations/integrations.module'; import { IntegrationsModule } from './integrations/integrations.module';
import { MailModule } from './mail/mail.module'; import { MailModule } from './mail/mail.module';
import { PrinterModule } from './printer/printer.module'; import { PrinterModule } from './printer/printer.module';
@ -32,6 +33,7 @@ import { UsersModule } from './users/users.module';
FontsModule, FontsModule,
IntegrationsModule, IntegrationsModule,
PrinterModule, PrinterModule,
HealthModule,
], ],
providers: [ providers: [
{ {

View File

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

View File

@ -0,0 +1,11 @@
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 {}