Skip to content

Commit 7d894fa

Browse files
committedOct 22, 2022
✨ github auth
1 parent bcf3b6c commit 7d894fa

18 files changed

+826
-9
lines changed
 

‎.env.example

+6
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@ SECRET_KEY=YOUR_JWT_SECRET_KEY
55
EMAIL=YOUR_EMAIL
66

77
EMAIL_PASSWORD=YOUR_EMAIL_APP_PASSWORD
8+
9+
GITHUB_CLIENT_ID=YOUR_GITHUB_CLIENT_ID
10+
11+
GITHUB_CLIENT_SECRET=YOUR_GITHUB_CLIENT_SECRET
12+
13+
GITHUB_SECRET_KEY=GITHUB_SECRET_KEY_TO_SIGN_JWT_FOR_STATE

‎graphql/profile.graphql

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# User type
2+
type User {
3+
id: ID!
4+
name: String!
5+
email: String!
6+
profilePicture: String
7+
description: String
8+
codeforcesUsername: String
9+
githubUsername: String
10+
leetcodeUsername: String
11+
createdAt: DateTime!
12+
updatedAt: DateTime!
13+
password: String!
14+
isVerified: Boolean!
15+
followedByIds: [ID!]!
16+
followingIds: [ID!]!
17+
}
18+
19+
type GithubAuth {
20+
id: ID!
21+
state: String!
22+
createdAt: DateTime!
23+
userId: ID!
24+
}
25+
26+
input AuthorizeGithubInput {
27+
userId: ID!
28+
}
29+
30+
type AuthorizeGithubOutput {
31+
state: String!
32+
url: String!
33+
}
34+
35+
type Mutation {
36+
# Authorize user with Github
37+
authorizeGithub(input: AuthorizeGithubInput!): AuthorizeGithubOutput!
38+
}

‎nest-cli.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
"collection": "@nestjs/schematics",
44
"sourceRoot": "src",
55
"compilerOptions": {
6-
"assets": [{"include": "mail/templates/**/*", "outDir": "dist/src"}],
6+
"assets": [
7+
{ "include": "mail/templates/**/*", "outDir": "dist/src" },
8+
{ "include": "views/*", "outDir": "dist/src" }
9+
],
710
"watchAssets": true
811
}
912
}

‎package.json

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"dependencies": {
2828
"@nestjs-modules/mailer": "^1.8.1",
2929
"@nestjs/apollo": "^10.1.3",
30+
"@nestjs/axios": "^0.1.0",
3031
"@nestjs/common": "^9.0.0",
3132
"@nestjs/config": "^2.2.0",
3233
"@nestjs/core": "^9.0.0",
@@ -41,6 +42,7 @@
4142
"graphql": "^16.6.0",
4243
"graphql-scalars": "^1.19.0",
4344
"handlebars": "^4.7.7",
45+
"hbs": "^4.2.0",
4446
"jsonwebtoken": "^8.5.1",
4547
"nodemailer": "^6.8.0",
4648
"prisma": "^4.4.0",

‎prisma/schema.prisma

+7
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,10 @@ model Token {
3232
email String @unique
3333
createdAt DateTime @default(now())
3434
}
35+
36+
model GithubAuth {
37+
id String @id @default(auto()) @map("_id") @db.ObjectId
38+
state String
39+
createdAt DateTime @default(now())
40+
userId String @unique @db.ObjectId
41+
}

‎src/app.controller.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Controller, Get } from '@nestjs/common';
1+
import { Controller, Get, Query, Redirect, Render } from '@nestjs/common';
22
import { AppService } from './app.service';
3+
import { GithubCallbackQuery } from './constants/profile.types';
34

45
@Controller()
56
export class AppController {
@@ -9,4 +10,27 @@ export class AppController {
910
getHello(): string {
1011
return this.appService.getHello2();
1112
}
13+
14+
@Get('/error')
15+
@Render('error')
16+
error(@Query('message') message: string) {
17+
return { message: message || 'Error' };
18+
}
19+
20+
@Get('success')
21+
@Render('success')
22+
success(@Query('message') message: string) {
23+
return { message: message || 'Success' };
24+
}
25+
26+
@Get('/github/callback')
27+
@Redirect()
28+
async githubCallback(@Query() query: GithubCallbackQuery) {
29+
try {
30+
const response = await this.appService.githubCallback(query);
31+
return { url: `/api/success?message=${response}` };
32+
} catch (error) {
33+
return { url: `/api/error?message=${error.message}` };
34+
}
35+
}
1236
}

‎src/app.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { MailModule } from './mail/mail.module';
99
import { ConfigModule } from '@nestjs/config';
1010
import { ProfileService } from './profile/profile.service';
1111
import { ProfileModule } from './profile/profile.module';
12+
import { PrismaService } from 'prisma/prisma.service';
1213

1314
@Module({
1415
imports: [
@@ -25,6 +26,6 @@ import { ProfileModule } from './profile/profile.module';
2526
ProfileModule,
2627
],
2728
controllers: [AppController],
28-
providers: [AppService, ProfileService],
29+
providers: [AppService, ProfileService, PrismaService],
2930
})
3031
export class AppModule {}

‎src/app.service.ts

+82
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,93 @@
11
import { Injectable } from '@nestjs/common';
2+
import axios from 'axios';
3+
import { BadRequestException } from '@nestjs/common';
4+
import {
5+
GITHUB_CLIENT_ID,
6+
GITHUB_CLIENT_SECRET,
7+
GITHUB_SECRET_KEY,
8+
} from './constants/env';
9+
import { PrismaService } from 'prisma/prisma.service';
10+
import { GithubCallbackQuery } from './constants/profile.types';
11+
import * as jwt from 'jsonwebtoken';
212

313
@Injectable()
414
export class AppService {
15+
constructor(private prisma: PrismaService) {}
16+
517
getHello(): string {
618
return 'Hello from B-704';
719
}
820
getHello2(): string {
921
return '8 CPI Boi - Akhilesh Manda';
1022
}
23+
24+
async githubCallback(query: GithubCallbackQuery): Promise<string> {
25+
const { code, state } = query;
26+
if (!code || !state) {
27+
throw new BadRequestException('Invalid request');
28+
}
29+
const { userId } = jwt.verify(state, GITHUB_SECRET_KEY) as {
30+
userId: string;
31+
iat: number;
32+
};
33+
console.log('userId', userId);
34+
if (!userId) {
35+
throw new BadRequestException('Invalid state');
36+
}
37+
const user = await this.prisma.user.findUnique({
38+
where: { id: userId },
39+
});
40+
if (!user) {
41+
throw new BadRequestException('User not found');
42+
}
43+
if (!user.isVerified) {
44+
throw new BadRequestException('User is not verified');
45+
}
46+
const githubAuth = await this.prisma.githubAuth.findUnique({
47+
where: { userId },
48+
});
49+
if (!githubAuth) {
50+
throw new Error(
51+
'Github auth codes are valid only for 10 minutes, please try again',
52+
);
53+
}
54+
if (githubAuth.state !== state) {
55+
throw new Error('Invalid state');
56+
}
57+
try {
58+
const response: any = await axios.post(
59+
`https://github.com/login/oauth/access_token?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}&code=${code}`,
60+
{
61+
headers: {
62+
'Content-Type': 'application/json',
63+
Accept: 'application/json',
64+
},
65+
},
66+
);
67+
console.log(
68+
`https://github.com/login/oauth/access_token?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}&code=${code}`,
69+
);
70+
let access_token = '';
71+
let i = 0;
72+
while (true) if (response.data[i++] === '=') break;
73+
for (; i < response.data.length; i++) {
74+
if (response.data[i] === '&') break;
75+
access_token += response.data[i];
76+
}
77+
console.log(response.data);
78+
await this.prisma.githubAuth.deleteMany({
79+
where: { id: userId },
80+
});
81+
console.log('access_token', access_token);
82+
await this.prisma.user.update({
83+
where: { id: userId },
84+
data: {
85+
githubUsername: access_token,
86+
},
87+
});
88+
return 'Github account linked successfully';
89+
} catch (error) {
90+
throw new Error('Github code is either invalid or expired');
91+
}
92+
}
1193
}

‎src/constants/env.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
import * as dotenv from 'dotenv';
22
dotenv.config();
33

4-
export const { DATABASE_URL, SECRET_KEY, EMAIL, EMAIL_PASSWORD } = process.env;
4+
export const {
5+
DATABASE_URL,
6+
SECRET_KEY,
7+
EMAIL,
8+
EMAIL_PASSWORD,
9+
GITHUB_CLIENT_ID,
10+
GITHUB_CLIENT_SECRET,
11+
GITHUB_SECRET_KEY,
12+
} = process.env;

‎src/constants/profile.types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface GithubCallbackQuery {
2+
code: string;
3+
state: string;
4+
}

‎src/graphql.types.ts

+18
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export class CheckCodeInput {
3636
email: string;
3737
}
3838

39+
export class AuthorizeGithubInput {
40+
userId: string;
41+
}
42+
3943
export class User {
4044
id: string;
4145
name: string;
@@ -80,11 +84,25 @@ export abstract class IMutation {
8084
abstract resetPassword(input: ResetPasswordInput): string | Promise<string>;
8185

8286
abstract checkCode(input: CheckCodeInput): CheckCodeOutptut | Promise<CheckCodeOutptut>;
87+
88+
abstract authorizeGithub(input: AuthorizeGithubInput): AuthorizeGithubOutput | Promise<AuthorizeGithubOutput>;
8389
}
8490

8591
export abstract class IQuery {
8692
abstract _dummy(): Nullable<string> | Promise<Nullable<string>>;
8793
}
8894

95+
export class GithubAuth {
96+
id: string;
97+
state: string;
98+
createdAt: DateTime;
99+
userId: string;
100+
}
101+
102+
export class AuthorizeGithubOutput {
103+
state: string;
104+
url: string;
105+
}
106+
89107
export type DateTime = any;
90108
type Nullable<T> = T | null;

‎src/main.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { NestFactory } from '@nestjs/core';
22
import { AppModule } from './app.module';
3+
import { NestExpressApplication } from '@nestjs/platform-express';
4+
import { join } from 'path';
35
import * as dotenv from 'dotenv';
46
dotenv.config();
57

68
const PORT = process.env.PORT || 8080;
79

810
async function bootstrap() {
9-
const app = await NestFactory.create(AppModule);
11+
const app = await NestFactory.create<NestExpressApplication>(AppModule);
1012
app.enableCors();
1113
app.setGlobalPrefix('api');
14+
app.setBaseViewsDir(join(__dirname, './', 'views'));
15+
app.setViewEngine('hbs');
16+
1217
await app.listen(PORT);
1318
}
1419
bootstrap();

‎src/profile/profile.module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ import { ProfileResolver } from './profile.resolver';
44
import { PrismaService } from 'prisma/prisma.service';
55

66
@Module({
7-
providers: [PrismaService, ProfileResolver, ProfileService],
7+
providers: [PrismaService, ProfileResolver, ProfileService, PrismaService],
88
})
99
export class ProfileModule {}

‎src/profile/profile.resolver.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { Resolver } from '@nestjs/graphql';
1+
import { Resolver, Mutation, Args } from '@nestjs/graphql';
2+
import { AuthorizeGithubInput } from 'src/graphql.types';
23
import { ProfileService } from './profile.service';
34

45
@Resolver()
56
export class ProfileResolver {
67
constructor(private readonly profileService: ProfileService) {}
8+
9+
@Mutation('authorizeGithub')
10+
async authorizeGithub(@Args('input') input: AuthorizeGithubInput) {
11+
return this.profileService.authorizeGithub(input);
12+
}
713
}

‎src/profile/profile.service.ts

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,64 @@
11
import { Injectable } from '@nestjs/common';
2+
import { BadRequestException } from '@nestjs/common';
3+
import * as jwt from 'jsonwebtoken';
4+
5+
import { PrismaService } from 'prisma/prisma.service';
6+
import { GITHUB_CLIENT_ID, GITHUB_SECRET_KEY } from 'src/constants/env';
7+
import { AuthorizeGithubInput, AuthorizeGithubOutput } from 'src/graphql.types';
28

39
@Injectable()
4-
export class ProfileService {}
10+
export class ProfileService {
11+
constructor(private prisma: PrismaService) {}
12+
13+
async authorizeGithub(
14+
data: AuthorizeGithubInput,
15+
): Promise<AuthorizeGithubOutput> {
16+
const { userId } = data;
17+
const user = await this.prisma.user.findUnique({
18+
where: { id: userId },
19+
});
20+
if (!user) {
21+
throw new BadRequestException('User not found');
22+
}
23+
const state = this.createState(userId);
24+
await this.clearGithubAuthTokens(userId);
25+
const newState = encodeURIComponent(state);
26+
await this.prisma.githubAuth.create({
27+
data: {
28+
state: newState,
29+
userId,
30+
},
31+
});
32+
return {
33+
state: newState,
34+
url: `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&state=${newState}`,
35+
};
36+
}
37+
38+
/**
39+
* It deletes all GithubAuth records that were created more than 10 minutes ago
40+
* and that belong to the given userId.
41+
* @param {string} userId - The user's ID.
42+
*/
43+
async clearGithubAuthTokens(userId: string) {
44+
await this.prisma.githubAuth.deleteMany({
45+
where: {
46+
createdAt: {
47+
lte: new Date(Date.now() - 10 * 60 * 1000),
48+
},
49+
},
50+
});
51+
await this.prisma.githubAuth.deleteMany({
52+
where: { userId },
53+
});
54+
}
55+
56+
/**
57+
* It takes a userId and returns a signed JWT
58+
* @param {string} userId - The user's ID.
59+
* @returns A JWT token that contains the userId.
60+
*/
61+
createState(userId: string) {
62+
return jwt.sign({ userId }, GITHUB_SECRET_KEY);
63+
}
64+
}

‎src/views/error.hbs

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
{{! Template credits: https://beefree.io/ }}
2+
3+
<html
4+
lang='en'
5+
xmlns:o='urn:schemas-microsoft-com:office:office'
6+
xmlns:v='urn:schemas-microsoft-com:vml'
7+
>
8+
9+
<head>
10+
<title></title>
11+
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
12+
<meta content='width=device-width, initial-scale=1.0' name='viewport' />
13+
<link
14+
href='https://fonts.googleapis.com/css?family=Montserrat'
15+
rel='stylesheet'
16+
type='text/css'
17+
/>
18+
<link
19+
href='https://fonts.googleapis.com/css?family=Open+Sans'
20+
rel='stylesheet'
21+
type='text/css'
22+
/>
23+
<style>
24+
* { box-sizing: border-box; } body { margin: 0; padding: 0; }
25+
a[x-apple-data-detectors] { color: inherit !important; text-decoration:
26+
inherit !important; } #MessageViewBody a { color: inherit;
27+
text-decoration: none; } p { line-height: inherit } .desktop_hide,
28+
.desktop_hide table { mso-hide: all; display: none; max-height: 0px;
29+
overflow: hidden; } @media (max-width:720px) { .desktop_hide
30+
table.icons-inner, .social_block.desktop_hide .social-table { display:
31+
inline-block !important; } .icons-inner { text-align: center; }
32+
.icons-inner td { margin: 0 auto; } .row-content { width: 100% !important;
33+
} .mobile_hide { display: none; } .stack .column { width: 100%; display:
34+
block; } .mobile_hide { min-height: 0; max-height: 0; max-width: 0;
35+
overflow: hidden; font-size: 0px; } .desktop_hide, .desktop_hide table {
36+
display: table !important; max-height: none !important; } }
37+
</style>
38+
</head>
39+
40+
<body
41+
style='margin: 0; background-color: #FFFFFF; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;'
42+
>
43+
<table
44+
border='0'
45+
cellpadding='0'
46+
cellspacing='0'
47+
class='nl-container'
48+
role='presentation'
49+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #FFFFFF;'
50+
width='100%'
51+
>
52+
<tbody>
53+
<tr>
54+
<td>
55+
<table
56+
align='center'
57+
border='0'
58+
cellpadding='0'
59+
cellspacing='0'
60+
class='row row-5'
61+
role='presentation'
62+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #F4F4F4;'
63+
width='100%'
64+
>
65+
<tbody>
66+
<tr>
67+
<td>
68+
<table
69+
align='center'
70+
border='0'
71+
cellpadding='0'
72+
cellspacing='0'
73+
class='row-content stack'
74+
role='presentation'
75+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #000000; width: 700px;'
76+
width='700'
77+
>
78+
<tbody>
79+
<tr>
80+
<td
81+
class='column column-1'
82+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top; padding-top: 5px; padding-bottom: 5px; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;'
83+
width='100%'
84+
>
85+
<table
86+
border='0'
87+
cellpadding='0'
88+
cellspacing='0'
89+
class='text_block block-1'
90+
role='presentation'
91+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;'
92+
width='100%'
93+
>
94+
<tr>
95+
<td
96+
class='pad'
97+
style='padding-left:30px;padding-right:30px;padding-top:10px;padding-bottom:30px; text-align:center'
98+
>
99+
<div
100+
style="font-family: 'Trebuchet MS', Tahoma, sans-serif;"
101+
>
102+
<div
103+
class=''
104+
style="font-size: 12px; font-family: 'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif; mso-line-height-alt: 14.399999999999999px; color: #cc0000; line-height: 1.2;"
105+
>
106+
<p
107+
style='margin: 0; font-size: 14px; mso-line-height-alt: 16.8px;'
108+
>
109+
<strong><span
110+
style='font-size:40px;'
111+
>{{message}}</span></strong>
112+
</p>
113+
</div>
114+
</div>
115+
</td>
116+
</tr>
117+
</table>
118+
<table
119+
border='0'
120+
cellpadding='0'
121+
cellspacing='0'
122+
class='text_block block-2'
123+
role='presentation'
124+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;'
125+
width='100%'
126+
>
127+
<tr>
128+
<td
129+
class='pad'
130+
style='padding-bottom:50px;padding-left:30px;padding-right:30px;padding-top:15px; text-align:center;'
131+
>
132+
<div style='font-family: sans-serif'>
133+
<div
134+
class=''
135+
style='font-size: 12px; mso-line-height-alt: 18px; color: #555555; line-height: 1.5; font-family: Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif;'
136+
>
137+
<p
138+
style='margin: 0; font-size: 12px; mso-line-height-alt: 18px;'
139+
><strong><span style='font-size:20px;'>You
140+
can now close this window and retry</span></strong></p>
141+
</div>
142+
</div>
143+
</td>
144+
</tr>
145+
</table>
146+
</td>
147+
</tr>
148+
</tbody>
149+
</table>
150+
</td>
151+
</tr>
152+
</tbody>
153+
</table>
154+
{{! Footer Start }}
155+
<table
156+
align='center'
157+
border='0'
158+
cellpadding='0'
159+
cellspacing='0'
160+
class='row row-9'
161+
role='presentation'
162+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #FFFFFF;'
163+
width='100%'
164+
>
165+
<tbody>
166+
<tr>
167+
<td>
168+
<table
169+
align='center'
170+
border='0'
171+
cellpadding='0'
172+
cellspacing='0'
173+
class='row-content stack'
174+
role='presentation'
175+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #FFFFFF; color: #000000; width: 700px;'
176+
width='700'
177+
>
178+
<tbody>
179+
<tr>
180+
<td
181+
class='column column-1'
182+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top; padding-top: 15px; padding-bottom: 35px; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;'
183+
width='100%'
184+
>
185+
<table
186+
border='0'
187+
cellpadding='10'
188+
cellspacing='0'
189+
class='text_block block-1'
190+
role='presentation'
191+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;'
192+
width='100%'
193+
>
194+
<tr>
195+
<td class='pad'>
196+
<div style='font-family: sans-serif'>
197+
<div
198+
class=''
199+
style='font-size: 12px; mso-line-height-alt: 18px; color: #838383; line-height: 1.5; font-family: Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif;'
200+
>
201+
<p
202+
style='margin: 0; font-size: 14px; text-align: center; mso-line-height-alt: 21px; padding-bottom: 10px;'
203+
>
204+
<span
205+
style='color:#555555;font-size:20px;'
206+
>
207+
<strong>
208+
Programmer Profile
209+
</strong>
210+
</span>
211+
</p>
212+
<p
213+
style='margin: 0; font-size: 16px; text-align: center; mso-line-height-alt: 21px;color: #555555;'
214+
>
215+
Made with ❤️ by
216+
<a
217+
style='text-decoration: none;color:#3D3BEE;'
218+
href='https://github.com/kalashshah'
219+
>Kalash Shah</a>
220+
and
221+
<a
222+
style='text-decoration: none;color:#3D3BEE;'
223+
href=' https://github.com/AkhileshManda'
224+
>Akhilesh Manda
225+
</a>
226+
</p>
227+
<p
228+
style='margin: 0; font-size: 16px; text-align: center; mso-line-height-alt: 21px;'
229+
>
230+
Visit us on
231+
<a
232+
href=' https://github.com/kalashshah/programmer-profile-backend'
233+
style='text-decoration: none;color:#3D3BEE;'
234+
>Github</a>
235+
</p>
236+
</div>
237+
</div>
238+
</td>
239+
</tr>
240+
</table>
241+
</td>
242+
</tr>
243+
</tbody>
244+
</table>
245+
</td>
246+
</tr>
247+
</tbody>
248+
</table>
249+
{{! Footer End }}
250+
</td>
251+
</tr>
252+
</tbody>
253+
</table>
254+
</body>
255+
256+
</html>

‎src/views/success.hbs

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
{{! Template credits: https://beefree.io/ }}
2+
3+
<html
4+
lang='en'
5+
xmlns:o='urn:schemas-microsoft-com:office:office'
6+
xmlns:v='urn:schemas-microsoft-com:vml'
7+
>
8+
9+
<head>
10+
<title></title>
11+
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
12+
<meta content='width=device-width, initial-scale=1.0' name='viewport' />
13+
<link
14+
href='https://fonts.googleapis.com/css?family=Montserrat'
15+
rel='stylesheet'
16+
type='text/css'
17+
/>
18+
<link
19+
href='https://fonts.googleapis.com/css?family=Open+Sans'
20+
rel='stylesheet'
21+
type='text/css'
22+
/>
23+
<style>
24+
* { box-sizing: border-box; } body { margin: 0; padding: 0; }
25+
a[x-apple-data-detectors] { color: inherit !important; text-decoration:
26+
inherit !important; } #MessageViewBody a { color: inherit;
27+
text-decoration: none; } p { line-height: inherit } .desktop_hide,
28+
.desktop_hide table { mso-hide: all; display: none; max-height: 0px;
29+
overflow: hidden; } @media (max-width:720px) { .desktop_hide
30+
table.icons-inner, .social_block.desktop_hide .social-table { display:
31+
inline-block !important; } .icons-inner { text-align: center; }
32+
.icons-inner td { margin: 0 auto; } .row-content { width: 100% !important;
33+
} .mobile_hide { display: none; } .stack .column { width: 100%; display:
34+
block; } .mobile_hide { min-height: 0; max-height: 0; max-width: 0;
35+
overflow: hidden; font-size: 0px; } .desktop_hide, .desktop_hide table {
36+
display: table !important; max-height: none !important; } }
37+
</style>
38+
</head>
39+
40+
<body
41+
style='margin: 0; background-color: #FFFFFF; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;'
42+
>
43+
<table
44+
border='0'
45+
cellpadding='0'
46+
cellspacing='0'
47+
class='nl-container'
48+
role='presentation'
49+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #FFFFFF;'
50+
width='100%'
51+
>
52+
<tbody>
53+
<tr>
54+
<td>
55+
<table
56+
align='center'
57+
border='0'
58+
cellpadding='0'
59+
cellspacing='0'
60+
class='row row-5'
61+
role='presentation'
62+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #F4F4F4;'
63+
width='100%'
64+
>
65+
<tbody>
66+
<tr>
67+
<td>
68+
<table
69+
align='center'
70+
border='0'
71+
cellpadding='0'
72+
cellspacing='0'
73+
class='row-content stack'
74+
role='presentation'
75+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #000000; width: 700px;'
76+
width='700'
77+
>
78+
<tbody>
79+
<tr>
80+
<td
81+
class='column column-1'
82+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top; padding-top: 5px; padding-bottom: 5px; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;'
83+
width='100%'
84+
>
85+
<table
86+
border='0'
87+
cellpadding='0'
88+
cellspacing='0'
89+
class='text_block block-1'
90+
role='presentation'
91+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;'
92+
width='100%'
93+
>
94+
<tr>
95+
<td
96+
class='pad'
97+
style='padding-left:30px;padding-right:30px;padding-top:10px;padding-bottom:30px; text-align:center'
98+
>
99+
<div
100+
style="font-family: 'Trebuchet MS', Tahoma, sans-serif;"
101+
>
102+
<div
103+
class=''
104+
style="font-size: 12px; font-family: 'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif; mso-line-height-alt: 14.399999999999999px; color: #55b663; line-height: 1.2;"
105+
>
106+
<p
107+
style='margin: 0; font-size: 14px; mso-line-height-alt: 16.8px;'
108+
>
109+
<strong><span
110+
style='font-size:40px;'
111+
>{{message}}</span></strong>
112+
</p>
113+
</div>
114+
</div>
115+
</td>
116+
</tr>
117+
</table>
118+
<table
119+
border='0'
120+
cellpadding='0'
121+
cellspacing='0'
122+
class='text_block block-2'
123+
role='presentation'
124+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;'
125+
width='100%'
126+
>
127+
<tr>
128+
<td
129+
class='pad'
130+
style='padding-bottom:50px;padding-left:30px;padding-right:30px;padding-top:15px; text-align:center;'
131+
>
132+
<div style='font-family: sans-serif'>
133+
<div
134+
class=''
135+
style='font-size: 12px; mso-line-height-alt: 18px; color: #555555; line-height: 1.5; font-family: Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif;'
136+
>
137+
<p
138+
style='margin: 0; font-size: 12px; mso-line-height-alt: 18px;'
139+
><strong><span style='font-size:20px;'>You
140+
can now close this window and
141+
continue on the application</span></strong></p>
142+
</div>
143+
</div>
144+
</td>
145+
</tr>
146+
</table>
147+
</td>
148+
</tr>
149+
</tbody>
150+
</table>
151+
</td>
152+
</tr>
153+
</tbody>
154+
</table>
155+
{{! Footer Start }}
156+
<table
157+
align='center'
158+
border='0'
159+
cellpadding='0'
160+
cellspacing='0'
161+
class='row row-9'
162+
role='presentation'
163+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #FFFFFF;'
164+
width='100%'
165+
>
166+
<tbody>
167+
<tr>
168+
<td>
169+
<table
170+
align='center'
171+
border='0'
172+
cellpadding='0'
173+
cellspacing='0'
174+
class='row-content stack'
175+
role='presentation'
176+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #FFFFFF; color: #000000; width: 700px;'
177+
width='700'
178+
>
179+
<tbody>
180+
<tr>
181+
<td
182+
class='column column-1'
183+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; font-weight: 400; text-align: left; vertical-align: top; padding-top: 15px; padding-bottom: 35px; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;'
184+
width='100%'
185+
>
186+
<table
187+
border='0'
188+
cellpadding='10'
189+
cellspacing='0'
190+
class='text_block block-1'
191+
role='presentation'
192+
style='mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;'
193+
width='100%'
194+
>
195+
<tr>
196+
<td class='pad'>
197+
<div style='font-family: sans-serif'>
198+
<div
199+
class=''
200+
style='font-size: 12px; mso-line-height-alt: 18px; color: #838383; line-height: 1.5; font-family: Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif;'
201+
>
202+
<p
203+
style='margin: 0; font-size: 14px; text-align: center; mso-line-height-alt: 21px; padding-bottom: 10px;'
204+
>
205+
<span
206+
style='color:#555555;font-size:20px;'
207+
>
208+
<strong>
209+
Programmer Profile
210+
</strong>
211+
</span>
212+
</p>
213+
<p
214+
style='margin: 0; font-size: 16px; text-align: center; mso-line-height-alt: 21px;color: #555555;'
215+
>
216+
Made with ❤️ by
217+
<a
218+
style='text-decoration: none;color:#3D3BEE;'
219+
href='https://github.com/kalashshah'
220+
>Kalash Shah</a>
221+
and
222+
<a
223+
style='text-decoration: none;color:#3D3BEE;'
224+
href=' https://github.com/AkhileshManda'
225+
>Akhilesh Manda
226+
</a>
227+
</p>
228+
<p
229+
style='margin: 0; font-size: 16px; text-align: center; mso-line-height-alt: 21px;'
230+
>
231+
Visit us on
232+
<a
233+
href=' https://github.com/kalashshah/programmer-profile-backend'
234+
style='text-decoration: none;color:#3D3BEE;'
235+
>Github</a>
236+
</p>
237+
</div>
238+
</div>
239+
</td>
240+
</tr>
241+
</table>
242+
</td>
243+
</tr>
244+
</tbody>
245+
</table>
246+
</td>
247+
</tr>
248+
</tbody>
249+
</table>
250+
{{! Footer End }}
251+
</td>
252+
</tr>
253+
</tbody>
254+
</table>
255+
</body>
256+
257+
</html>

‎yarn.lock

+41-1
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,13 @@
857857
lodash.omit "4.5.0"
858858
tslib "2.4.0"
859859

860+
"@nestjs/axios@^0.1.0":
861+
version "0.1.0"
862+
resolved "https://registry.yarnpkg.com/@nestjs/axios/-/axios-0.1.0.tgz#6cf93df11ef93b598b3c7411adb980eedd13b3e3"
863+
integrity sha512-b2TT2X6BFbnNoeteiaxCIiHaFcSbVW+S5yygYqiIq5i6H77yIU3IVuLdpQkHq8/EqOWFwMopLN8jdkUT71Am9w==
864+
dependencies:
865+
axios "0.27.2"
866+
860867
"@nestjs/cli@^9.0.0":
861868
version "9.1.4"
862869
resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-9.1.4.tgz#a5a88d02d90a5eff279aa00af3c84ff74882e63c"
@@ -1899,6 +1906,14 @@ asynckit@^0.4.0:
18991906
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
19001907
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
19011908

1909+
axios@0.27.2:
1910+
version "0.27.2"
1911+
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
1912+
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
1913+
dependencies:
1914+
follow-redirects "^1.14.9"
1915+
form-data "^4.0.0"
1916+
19021917
babel-jest@^28.1.3:
19031918
version "28.1.3"
19041919
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5"
@@ -3314,6 +3329,16 @@ flatted@^3.1.0:
33143329
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
33153330
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
33163331

3332+
follow-redirects@^1.14.9:
3333+
version "1.15.2"
3334+
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
3335+
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
3336+
3337+
foreachasync@^3.0.0:
3338+
version "3.0.0"
3339+
resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6"
3340+
integrity sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==
3341+
33173342
fork-ts-checker-webpack-plugin@7.2.13:
33183343
version "7.2.13"
33193344
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz#51ffd6a2f96f03ab64b92f8aedf305dbf3dee0f1"
@@ -3577,7 +3602,7 @@ graphql@^16.6.0:
35773602
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb"
35783603
integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==
35793604

3580-
handlebars@^4.7.6, handlebars@^4.7.7:
3605+
handlebars@4.7.7, handlebars@^4.7.6, handlebars@^4.7.7:
35813606
version "4.7.7"
35823607
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
35833608
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
@@ -3623,6 +3648,14 @@ has@^1.0.3:
36233648
dependencies:
36243649
function-bind "^1.1.1"
36253650

3651+
hbs@^4.2.0:
3652+
version "4.2.0"
3653+
resolved "https://registry.yarnpkg.com/hbs/-/hbs-4.2.0.tgz#10e40dcc24d5be7342df9636316896617542a32b"
3654+
integrity sha512-dQwHnrfWlTk5PvG9+a45GYpg0VpX47ryKF8dULVd6DtwOE6TEcYQXQ5QM6nyOx/h7v3bvEQbdn19EDAcfUAgZg==
3655+
dependencies:
3656+
handlebars "4.7.7"
3657+
walk "2.3.15"
3658+
36263659
he@1.2.0, he@^1.2.0:
36273660
version "1.2.0"
36283661
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -6973,6 +7006,13 @@ void-elements@^3.1.0:
69737006
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
69747007
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
69757008

7009+
walk@2.3.15:
7010+
version "2.3.15"
7011+
resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.15.tgz#1b4611e959d656426bc521e2da5db3acecae2424"
7012+
integrity sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==
7013+
dependencies:
7014+
foreachasync "^3.0.0"
7015+
69767016
walker@^1.0.8:
69777017
version "1.0.8"
69787018
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"

0 commit comments

Comments
 (0)
Please sign in to comment.