Skip to content

Commit f691d8c

Browse files
committed
approve reject application
1 parent fdbfe09 commit f691d8c

File tree

15 files changed

+125
-64
lines changed

15 files changed

+125
-64
lines changed

netlify/functions-src/functions/data/mentors.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import { ObjectId } from 'mongodb';
21
import type { Application } from '../modules/mentors/types';
32
import type { UpsertResult } from './types';
43
import { upsertEntityByCondition } from './utils';
54
import { getCollection } from '../utils/db';
5+
import { ObjectId, type Filter, type MatchKeysAndValues } from 'mongodb';
66

7-
export const upsertApplication = async (userId: ObjectId): UpsertResult<Application> => {
8-
const applicationPayload = {
9-
user: userId,
10-
status: 'pending',
7+
export const upsertApplication = async (application: MatchKeysAndValues<Application>): UpsertResult<Application> => {
8+
const filter: Filter<Application> = {};
9+
if (application._id) {
10+
filter._id = new ObjectId(application._id);
1111
}
1212

13-
const { data: application, isNew } = await upsertEntityByCondition<Application>(
13+
const { data: upsertedApplication, isNew } = await upsertEntityByCondition<Application>(
1414
'applications',
15-
{ user: userId, status: 'pending' },
16-
applicationPayload
15+
filter,
16+
application,
1717
);
1818

1919
return {
20-
data: application,
20+
data: upsertedApplication,
2121
isNew,
2222
};
2323
}
@@ -26,7 +26,7 @@ export const getApplications = async (status?: string): Promise<Application[]> =
2626
const collection = getCollection<Application>('applications');
2727
const filter: any = {};
2828
if (status) {
29-
filter.status = status?.toUpperCase();
29+
filter.status = status;
3030
}
3131

3232
const pipeline = [

netlify/functions-src/functions/data/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const upsertEntityByCondition = async <T extends OptionalId<unknown>>(
1111
const collection = getCollection<T>(collectionName);
1212
const { value: upsertedEntity, lastErrorObject } = await collection.findOneAndUpdate(
1313
condition,
14-
{ $setOnInsert: entity },
14+
{ $set: entity },
1515
{ upsert: true, returnDocument: 'after', includeResultMetadata: true }
1616
);
1717
const isNew = lastErrorObject?.updatedExisting === false;

netlify/functions-src/functions/hof/withRouter.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ import { error } from '../utils/response';
44
import type { ApiHandler, HttpMethod } from '../types';
55
import { DataError } from '../data/errors';
66

7-
type Route = [pattern: string, HttpMethod, ApiHandler];
7+
type Route = [pattern: string, HttpMethod | HttpMethod[], ApiHandler];
88
export type Routes = Route[];
99

10-
const findRouteByPath = (routes: Routes, path: string, httpMethod: string) => {
10+
const matchesHttpMethod = (allowedMethods: HttpMethod | HttpMethod[], requestMethod: HttpMethod): allowedMethods is HttpMethod => {
11+
if (Array.isArray(allowedMethods)) {
12+
return allowedMethods.includes(requestMethod);
13+
}
14+
return allowedMethods === requestMethod;
15+
}
16+
17+
const findRouteByPath = (routes: Routes, path: string, httpMethod: HttpMethod) => {
1118
const matchedRoutes = routes.filter(([pattern]) => {
1219
const urlPattern = new UrlPattern(pattern);
1320
return urlPattern.match(path);
@@ -19,12 +26,14 @@ const findRouteByPath = (routes: Routes, path: string, httpMethod: string) => {
1926
case 1:
2027
const [matchedRoute] = matchedRoutes;
2128
const [, routeHttpMethod] = matchedRoute;
22-
if (routeHttpMethod !== httpMethod) {
29+
if (!matchesHttpMethod(routeHttpMethod, httpMethod)) {
2330
throw new DataError(405, 'Method not allowed');
2431
}
2532
return matchedRoute;
2633
default:
27-
const route = matchedRoutes.find(([, HttpMethod]) => HttpMethod === httpMethod);
34+
const route = matchedRoutes.find(
35+
([, routeHttpMethod]) => matchesHttpMethod(routeHttpMethod, httpMethod)
36+
);
2837
if (!route) {
2938
throw new DataError(405, 'Method not allowed');
3039
}
@@ -49,7 +58,7 @@ export const withRouter = (routes: Routes): ApiHandler => {
4958
return async (event, context) => {
5059
try {
5160
const path = getAppPath(event.path);
52-
const route = findRouteByPath(routes, path, event.httpMethod);
61+
const route = findRouteByPath(routes, path, event.httpMethod as HttpMethod);
5362

5463
const { handler, params } = getRouteData(route, path);
5564

@@ -61,7 +70,12 @@ export const withRouter = (routes: Routes): ApiHandler => {
6170
}
6271
}
6372

64-
return await handler({ ...event, queryStringParameters: params }, context);
73+
const queryStringParameters = {
74+
...event.queryStringParameters,
75+
...params,
76+
};
77+
78+
return await handler({ ...event, queryStringParameters }, context);
6579
} catch (e) {
6680
console.error(e);
6781
if (e instanceof DataError) {

netlify/functions-src/functions/mentors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { withRouter } from './hof/withRouter';
55
import { handler as getMentorsHanler } from './modules/mentors/get';
66
import { handler as getApplicationsHandler } from './modules/mentors/applications/get';
77
import { handler as upsertApplicationHandler } from './modules/mentors/applications/post';
8+
import { handler as updateApplicationHandler } from './modules/mentors/applications/put';
89
import { Role } from './common/interfaces/user.interface';
910

1011
export const handler: ApiHandler = withDB(
1112
withRouter([
1213
['/', 'GET', withAuth(getMentorsHanler, { authRequired: false })],
1314
['/applications', 'GET', withAuth(getApplicationsHandler, { returnUser: true, role: Role.ADMIN })],
15+
['/applications/:applicationId', 'PUT', withAuth(updateApplicationHandler, { returnUser: true, role: Role.ADMIN })],
1416
['/applications', 'POST', withAuth(upsertApplicationHandler, { returnUser: true, })],
1517
// TODO: find out if needed
1618
// ['/:userId/applications', 'GET', withAuth(getUserApplicationsHandler, { returnUser: true })],

netlify/functions-src/functions/modules/mentors/applications/post.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import type { ApiHandler } from '../../../types';
44
import { success } from '../../../utils/response';
55
import type { Application } from '../types';
66

7-
export const handler: ApiHandler<Application, User> = async (_event, context) => {
8-
const { data, isNew } = await upsertApplication(context.user._id);
7+
export const handler: ApiHandler<Application, User> = async (event, context) => {
8+
const application = event.parsedBody!;
9+
const { data, isNew } = await upsertApplication({
10+
...application,
11+
user: context.user._id,
12+
status: 'Pending',
13+
});
914
return success({ data }, isNew ? 201 : 200);
1015
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ObjectId } from 'mongodb';
2+
import type { User } from '../../../common/interfaces/user.interface';
3+
import { upsertApplication } from '../../../data/mentors';
4+
import type { ApiHandler } from '../../../types';
5+
import type { ApplicationStatus } from '../types';
6+
import { success } from '../../../utils/response';
7+
8+
export const handler: ApiHandler<{ status: ApplicationStatus }, User> = async (event, context) => {
9+
const { applicationId } = event.queryStringParameters || {};
10+
const { status } = event.parsedBody || {};
11+
12+
if (!applicationId || !status) {
13+
return { statusCode: 400, body: 'Bad request' };
14+
}
15+
16+
const { data, isNew } = await upsertApplication({
17+
_id: new ObjectId(applicationId),
18+
status,
19+
});
20+
21+
return success({
22+
data,
23+
isNew,
24+
});
25+
}

netlify/functions-src/functions/modules/mentors/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ export interface Mentor {
1313
avatar?: string
1414
}
1515

16+
export type ApplicationStatus = 'Pending' | 'Approved' | 'Rejected';
1617
export type Application = OptionalId<{
1718
user: ObjectId;
18-
status: string;
19+
status: ApplicationStatus;
20+
reason?: string;
1921
}>;
2022

21-
2223
export interface GetMentorsQuery {
2324
tags?: string | string[]
2425
country?: string

netlify/functions-src/functions/utils/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export function withAuth(handler: ApiHandler, options: {
8080
}
8181

8282
// TODO: instead, set a custom prop on auth0 - is admin to save the call to the database and get it from the token
83+
// TODO: combine these 2 getCurrentUser calls
8384
if (role) {
8485
const currentUser = await getCurrentUser(decodedToken.sub)
8586
if (!currentUser.roles.includes(role)) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// MongoDB Playground
2+
// Use Ctrl+Space inside a snippet or a string literal to trigger completions.
3+
4+
const { stat } = require('fs');
5+
const { ObjectId } = require('mongodb');
6+
7+
// The current database to use.
8+
use('codingcoach');
9+
10+
// Create a new document in the collection.
11+
db.getCollection('applications').findOneAndUpdate(
12+
{_id: new ObjectId('67e99efa8eb43562ce98b410')},
13+
{ $set: { status: 'Pending' } },
14+
{ returnDocument: 'after' }
15+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// MongoDB Playground
2+
// Use Ctrl+Space inside a snippet or a string literal to trigger completions.
3+
4+
const { stat } = require('fs');
5+
6+
// The current database to use.
7+
use('codingcoach');
8+
9+
// Create a new document in the collection.
10+
db.getCollection('applications').find({status: 'Pending'});

0 commit comments

Comments
 (0)