Skip to content

Commit 3d3fbce

Browse files
authored
omg: migrate nest server to netlify functions (#977)
1 parent 8b8d43d commit 3d3fbce

File tree

128 files changed

+10916
-739
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+10916
-739
lines changed

.env

Lines changed: 0 additions & 6 deletions
This file was deleted.

.env.development

Lines changed: 0 additions & 6 deletions
This file was deleted.

.gitignore

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,79 @@ debug.log
4141

4242
# Next.js
4343
.next
44+
server/src/app.module.ts
45+
server/src/logger.ts
46+
server/src/main.ts
47+
server/src/config/index.ts
48+
server/src/database/database.module.ts
49+
server/src/database/database.providers.ts
50+
server/src/middlewares/auth.middleware.ts
51+
server/src/modules/admin/admin.controller.ts
52+
server/src/modules/admin/admin.module.ts
53+
server/src/modules/admin/__tests__/mentors.controller.spec.ts
54+
server/src/modules/common/auth0.service.ts
55+
server/src/modules/common/common.module.ts
56+
server/src/modules/common/common.providers.ts
57+
server/src/modules/common/file.service.ts
58+
server/src/modules/common/mentors.service.ts
59+
server/src/modules/common/users.service.ts
60+
server/src/modules/common/dto/application.dto.ts
61+
server/src/modules/common/dto/filter.dto.ts
62+
server/src/modules/common/dto/findOneParams.dto.ts
63+
server/src/modules/common/dto/mentorfilters.dto.ts
64+
server/src/modules/common/dto/pagination.dto.ts
65+
server/src/modules/common/dto/user-record.dto.ts
66+
server/src/modules/common/dto/user.dto.ts
67+
server/src/modules/common/interfaces/application.interface.ts
68+
server/src/modules/common/interfaces/filemeta.interface.ts
69+
server/src/modules/common/interfaces/user-record.interface.ts
70+
server/src/modules/common/interfaces/user.interface.ts
71+
server/src/modules/common/pipes/pagination.pipe.ts
72+
server/src/modules/common/schemas/application.schema.ts
73+
server/src/modules/common/schemas/user-record.schema.ts
74+
server/src/modules/common/schemas/user.schema.ts
75+
server/src/modules/email/email.module.ts
76+
server/src/modules/email/email.service.ts
77+
server/src/modules/email/interfaces/email.interface.ts
78+
server/src/modules/lists/favorites.controller.ts
79+
server/src/modules/lists/list.providers.ts
80+
server/src/modules/lists/lists.controller.ts
81+
server/src/modules/lists/lists.module.ts
82+
server/src/modules/lists/lists.service.ts
83+
server/src/modules/lists/__tests__/favorites.controller.spec.ts
84+
server/src/modules/lists/__tests__/lists.controller.spec.ts
85+
server/src/modules/lists/dto/list.dto.ts
86+
server/src/modules/lists/interfaces/list.interface.ts
87+
server/src/modules/lists/schemas/list.schema.ts
88+
server/src/modules/mentors/mentors.controller.ts
89+
server/src/modules/mentors/mentors.module.ts
90+
server/src/modules/mentors/__tests__/mentors.controller.spec.ts
91+
server/src/modules/mentorships/mentorships.controller.ts
92+
server/src/modules/mentorships/mentorships.module.ts
93+
server/src/modules/mentorships/mentorships.providers.ts
94+
server/src/modules/mentorships/mentorships.service.ts
95+
server/src/modules/mentorships/mentorshipsToDto.ts
96+
server/src/modules/mentorships/__tests__/mentorships.controller.spec.ts
97+
server/src/modules/mentorships/dto/mentorship.dto.ts
98+
server/src/modules/mentorships/dto/mentorshipSummary.dto.ts
99+
server/src/modules/mentorships/dto/mentorshipUpdatePayload.dto.ts
100+
server/src/modules/mentorships/interfaces/mentorship.interface.ts
101+
server/src/modules/mentorships/schemas/mentorship.schema.ts
102+
server/src/modules/reports/reports.controller.ts
103+
server/src/modules/reports/reports.module.ts
104+
server/src/modules/reports/reports.service.ts
105+
server/src/modules/reports/__tests__/reports.controller.spec.ts
106+
server/src/modules/reports/interfaces/totals.interface.ts
107+
server/src/modules/users/users.controller.ts
108+
server/src/modules/users/users.module.ts
109+
server/src/modules/users/__tests__/users.controller.spec.ts
110+
server/src/utils/mimes.ts
111+
server/src/utils/objectid.ts
112+
server/src/utils/request.d.ts
113+
114+
# Local Netlify folder
115+
.netlify
116+
netlify/functions/**/*.js
117+
# Environment variables
118+
.env*
44119
.netlify

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.next
22
build
3-
tsconfig.json
3+
tsconfig.json
4+
netlify

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
77

88

9-
[![Powered By Vercel](https://github.com/Coding-Coach/find-a-mentor/blob/3390fb723a8148c06ea88a7fdb972fd81b5a6064/src/assets/powered-by-vercel.svg)](https://vercel.com/codingcoach?utm_source=coding-coach&utm_campaign=oss)
10-
11-
129
## Support Us
1310

1411
CodingCoach is a FREE platform that is built and managed entirely by volunteers. As always, there are ongoing costs to run this site such as servers, domains, email, and more. Please consider becoming a patron so we can continue our mission of being accessible and free ❤️

api-types/errorCodes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export enum ErrorCodes {
2+
EmailNotVerified = 1,
3+
}

docs/auth.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Authentication System
2+
3+
We're using Auth0 to handle authentication in our application. This document outlines the authentication flow and how to set up your environment for development.
4+
5+
## Registration
6+
7+
1. User clicks on the "Sign Up" button.
8+
2. User is redirected to the Auth0 login page.
9+
3. User enters their email and password.
10+
4. Auth0 sends a verification email, which we leverage for the welcome email.
11+
5. User clicks on the verification link in the email.
12+
6. User is redirected to the application with a verification token. For more information about the redirection see the [docs](https://auth0.com/docs/customize/email/email-templates#configure-template-fields) - open the "Redirect the URL" section.
13+
> **ℹ️ Info**
14+
> Remember. The application.callback_domain variable will contain the origin of the first URL listed in the application's Allowed Callback URL list
15+
16+
## Authentication
17+
18+
1. User clicks on the "Log In" button.
19+
2. User is redirected to the Auth0 login page.
20+
3. User enters their email and password.
21+
4. Auth0 verifies the credentials and redirects the user back to the application with an access token which includes auth0id and an `email_verified` and `picture` added by a [custom action](https://manage.auth0.com/dashboard/eu/codingcoach/actions/library/details/e425e5f6-fcd0-4ec9-a3b5-68b3ef8eca75). `email_verified` is enabled by the `email` scope and `picture` is enabled by the `profile` scope.

netlify.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22
command = "yarn build"
33
publish = ".next"
44

5+
[functions]
6+
directory = "netlify/functions-src/functions"
7+
included_files = ["netlify/functions-src/functions/email/templates/**.html"]
8+
9+
[dev]
10+
command = "yarn start"
11+
512
[build.environment]
613
NEXT_TELEMETRY_DISABLED = "1"
714
NODE_VERSION = "18"
815

916
[[plugins]]
10-
package = "@netlify/plugin-nextjs"
17+
package = "@netlify/plugin-nextjs"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Config from '../config';
2+
3+
const auth0Domain = Config.auth0.backend.DOMAIN;
4+
5+
const getAdminAccessToken = async (): Promise<string> => {
6+
try {
7+
8+
const response = await fetch(`https://${auth0Domain}/oauth/token`, {
9+
method: 'POST',
10+
headers: {
11+
'Content-Type': 'application/json',
12+
},
13+
body: JSON.stringify({
14+
client_id: Config.auth0.backend.CLIENT_ID,
15+
client_secret: Config.auth0.backend.CLIENT_SECRET,
16+
audience: Config.auth0.backend.AUDIENCE,
17+
grant_type: 'client_credentials',
18+
}),
19+
});
20+
const data = await response.json();
21+
return data.access_token;
22+
} catch (error) {
23+
// eslint-disable-next-line no-console
24+
console.error('Error fetching admin access token:', error);
25+
throw new Error('Failed to fetch access token');
26+
}
27+
};
28+
29+
30+
export const deleteUser = async (userId: string): Promise<void> => {
31+
const accessToken = await getAdminAccessToken();
32+
if (!accessToken) {
33+
throw new Error('Failed to get access token');
34+
}
35+
36+
const response = await fetch(`https://${auth0Domain}/api/v2/users/${userId}`, {
37+
method: 'DELETE',
38+
headers: {
39+
Authorization: `Bearer ${accessToken}`,
40+
},
41+
});
42+
43+
if (!response.ok) {
44+
throw new Error(`Failed to delete user: ${response.statusText}`);
45+
}
46+
47+
return response.json();
48+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import axios, { type AxiosResponse } from 'axios'
2+
import Config from '../config'
3+
4+
class Auth0Service {
5+
private readonly auth0Domain = Config.auth0.backend.DOMAIN
6+
private readonly clientId = Config.auth0.backend.CLIENT_ID
7+
private readonly clientSecret = Config.auth0.backend.CLIENT_SECRET
8+
private readonly audience = Config.auth0.backend.AUDIENCE
9+
10+
async getAdminAccessToken(): Promise<AxiosResponse<{ access_token: string }>['data']> {
11+
try {
12+
const response = await axios.post(`https://${this.auth0Domain}/oauth/token`, {
13+
client_id: this.clientId,
14+
client_secret: this.clientSecret,
15+
audience: this.audience,
16+
grant_type: 'client_credentials',
17+
})
18+
return response.data
19+
} catch (error) {
20+
console.error('getAdminAccessToken, Error:', error)
21+
throw new Error('Error getting access token')
22+
}
23+
}
24+
25+
async getUserProfile(auth0Id: string): Promise<AxiosResponse<{ email: string, nickname: string, picture: string }>['data']> {
26+
try {
27+
const { access_token } = await this.getAdminAccessToken();
28+
const response = await axios.get(`https://${this.auth0Domain}/api/v2/users/${auth0Id}`, {
29+
headers: {
30+
Authorization: `Bearer ${access_token}`,
31+
},
32+
})
33+
return response.data
34+
} catch (error) {
35+
console.error('getUserProfile, Error:', error)
36+
throw new Error('Error getting user profile')
37+
}
38+
}
39+
40+
async deleteUser(accessToken: string, userId: string): Promise<void> {
41+
await axios.delete(`https://${this.auth0Domain}/api/v2/users/${userId}`, {
42+
headers: {
43+
Authorization: `Bearer ${accessToken}`,
44+
},
45+
})
46+
}
47+
48+
async createVerificationEmailTicket(
49+
auth0UserId: string,
50+
) {
51+
try {
52+
const { access_token: accessToken } = await this.getAdminAccessToken();
53+
const [provider, userId] = auth0UserId.split('|');
54+
const payload = {
55+
result_url: Config.urls.CLIENT_BASE_URL,
56+
user_id: auth0UserId,
57+
identity: { user_id: userId, provider },
58+
};
59+
60+
const response = await axios.post(
61+
`https://${Config.auth0.backend.DOMAIN}/api/v2/tickets/email-verification`,
62+
payload,
63+
{
64+
headers: {
65+
Authorization: `Bearer ${accessToken}`,
66+
'content-type': 'application/json',
67+
},
68+
},
69+
);
70+
71+
return response.data;
72+
} catch (error) {
73+
console.error('createVerificationEmailTicket, Error:', error)
74+
throw error;
75+
}
76+
}
77+
}
78+
79+
export const auth0Service = new Auth0Service();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ObjectId } from 'mongodb'
2+
import { Role } from '../interfaces/user.interface'
3+
4+
export class UserDto {
5+
_id: ObjectId
6+
auth0Id: string
7+
email: string
8+
name: string
9+
avatar?: string
10+
roles: Role[]
11+
12+
constructor(partial: Partial<UserDto>) {
13+
Object.assign(this, partial)
14+
}
15+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { ObjectId } from 'mongodb'
2+
3+
export enum ChannelName {
4+
EMAIL = 'email',
5+
SLACK = 'slack',
6+
LINKED = 'linkedin',
7+
FACEBOOK = 'facebook',
8+
TWITTER = 'twitter',
9+
GITHUB = 'github',
10+
WEBSITE = 'website',
11+
}
12+
13+
export interface Channel extends Document {
14+
readonly type: ChannelName;
15+
readonly id: string;
16+
}
17+
18+
export interface User {
19+
readonly _id: ObjectId;
20+
available?: boolean;
21+
auth0Id: string;
22+
email: string;
23+
name: string;
24+
avatar?: string;
25+
roles: Role[];
26+
country?: string;
27+
image?: {
28+
filename: string;
29+
};
30+
channels?: Channel[];
31+
createdAt: Date;
32+
spokenLanguages?: string[];
33+
tags?: string[];
34+
}
35+
36+
export type ApplicationUser = User & {
37+
email_verified: boolean;
38+
picture: string;
39+
}
40+
41+
export enum Role {
42+
ADMIN = 'Admin',
43+
MEMBER = 'Member',
44+
MENTOR = 'Mentor'
45+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const config = {
2+
mongo: {
3+
url: process.env.MONGO_DATABASE_URL,
4+
},
5+
auth0: {
6+
// to decode the token
7+
frontend: {
8+
CLIENT_ID: process.env.AUTH0_FRONTEND_CLIENT_ID,
9+
CLIENT_SECRET: process.env.AUTH0_FRONTEND_CLIENT_SECRET,
10+
DOMAIN: process.env.AUTH0_DOMAIN,
11+
},
12+
// To get access to auth0 admin features
13+
backend: {
14+
CLIENT_ID: process.env.AUTH0_BACKEND_CLIENT_ID,
15+
CLIENT_SECRET: process.env.AUTH0_BACKEND_CLIENT_SECRET!,
16+
DOMAIN: process.env.AUTH0_DOMAIN!,
17+
AUDIENCE: process.env.AUTH0_AUDIENCE!,
18+
},
19+
},
20+
sendGrid: {
21+
API_KEY: process.env.SENDGRID_API_KEY!,
22+
},
23+
sentry: {
24+
DSN: process.env.SENTRY_DSN,
25+
},
26+
email: {
27+
FROM: 'Coding Coach <no-reply@mail.codingcoach.io>',
28+
},
29+
files: {
30+
public: process.env.PUBLIC_FOLDER,
31+
avatars: `avatars`,
32+
},
33+
pagination: {
34+
limit: 20,
35+
},
36+
urls: {
37+
CLIENT_BASE_URL: process.env.CLIENT_BASE_URL,
38+
},
39+
};
40+
41+
export default config;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class DataError extends Error {
2+
constructor(public statusCode: number, public message: string = `Date Error: ${statusCode}`) {
3+
super(message);
4+
}
5+
}

0 commit comments

Comments
 (0)