Skip to content
Open
2 changes: 2 additions & 0 deletions serverless.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export the teamsLeave endpoint HackRU-Backend\src\functions\index.ts as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you want me to export the index.ts

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import declineInvitation from '@functions/teams/decline-invite';
import teamsJoin from '@functions/teams/join';
import teamsRead from '@functions/teams/read';
import disband from '@functions/teams/disband';
import teamLeave from '@functions/teams/leave';

import * as path from 'path';
import * as dotenv from 'dotenv';
Expand Down Expand Up @@ -80,6 +81,7 @@ const serverlessConfiguration: AWS = {
teamsJoin,
teamsRead,
disband,
teamLeave,
},
package: { individually: true, patterns: ['!.env*', '.env.vault'] },
custom: {
Expand Down
123 changes: 123 additions & 0 deletions src/functions/teams/leave/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';
import schema from './schema';
import { UserDocument, TeamDocument } from '../../../types';
import { MongoDB, validateToken, disbandTeam } from '../../../util'; //change to actual disband
import * as path from 'path';
import * as dotenv from 'dotenv';

//import fetch from 'node-fetch';

dotenv.config({ path: path.resolve(process.cwd(), '.env') });

const teamLeave: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
try {
const { auth_token: authToken, auth_email: authEmail, team_id: teamId } = event.body;

// 1. Validate auth token
const tokenValid = validateToken(authToken, process.env.JWT_SECRET, authEmail);
if (!tokenValid) {
return {
statusCode: 401,
body: JSON.stringify({ statusCode: 401, message: 'Unauthorized' }),
};
}

// 2. connect to mongoDB
const db = MongoDB.getInstance(process.env.MONGO_URI);
await db.connect();
const users = db.getCollection<UserDocument>('users');
const teams = db.getCollection<TeamDocument>('teams');

// 3. check if user exisits
const authUser = await users.findOne({ email: authEmail });
if (!authUser) {
return {
statusCode: 404,
body: JSON.stringify({ statusCode: 404, message: 'Auth user not found' }),
};
}

// 4. check if team exisits
const team = await teams.findOne({ team_id: teamId });
if (!team) {
return {
statusCode: 404,
body: JSON.stringify({ statusCode: 404, message: 'Team not found' }),
};
}

// 5. Check if team is disbanded
if (team.status == 'Disbanded') {
return {
statusCode: 400,
body: JSON.stringify({ statusCode: 400, message: 'Team already disbanded' }),
};
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do a validation check on leader email. Simple if exists check will be enough. The reason why we want to do this is because we may do a leader_email check later on. To be fair, a team's state should never be in this spot (where a team has no leader) but just to be safe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want this check to be done if the leader wants to leave the team or always when running this endpoint

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either or is fine. I think they are functionally equal so I'll leave it to your best judgement.

// 6. No team members
if (team.members.length + 1 == 0) {
return {
statusCode: 400,
body: JSON.stringify({ statusCode: 400, message: 'Empty team member list' }),
};
}

// 7. Check if user is in team
if (!team.members.includes(authEmail) && team.leader_email !== authEmail) {
return {
statusCode: 400,
body: JSON.stringify({ statusCode: 400, message: 'User not in team' }),
};
}

//grabs team info object
const teamInfo = authUser.team_info;

// 8. Check if user is team lead
if (teamInfo.role == 'leader') return await disbandTeam(authToken, authEmail, teamId);

// 9. Remove user from team
await teams.updateOne(
{ team_id: teamId, members: authUser.email },
{
$pull: {
members: authUser.email,
},
}
);

// 10. clear team_info and set confirmed_team

authUser.confirmed_team = false;
authUser.team_info = {
team_id: '',
role: null,
pending_invites: [],
};

// 11. update the MongoDB user
await users.updateOne(
{ email: authEmail },
{
$set: {
confirmed_team: authUser.confirmed_team,
team_info: authUser.team_info,
},
}
);

return {
statusCode: 200,
body: JSON.stringify({ message: 'Successfully left team' }),
};
} catch (error) {
console.error('Error deleting team member:', error);
return {
statusCode: 500,
body: JSON.stringify({ statusCode: 500, message: 'Internal server error' }),
};
}
};

export const main = middyfy(teamLeave);
20 changes: 20 additions & 0 deletions src/functions/teams/leave/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { handlerPath } from '@libs/handler-resolver';
import schema from './schema';

export default {
handler: `${handlerPath(__dirname)}/handler.main`,
events: [
{
http: {
method: 'post',
path: 'teams/leave',
cors: true,
request: {
schemas: {
'application/json': schema,
},
},
},
},
],
};
9 changes: 9 additions & 0 deletions src/functions/teams/leave/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
type: 'object',
properties: {
auth_token: { type: 'string' },
auth_email: { type: 'string', format: 'email' },
team_id: { type: 'string' },
},
required: ['auth_token', 'auth_email', 'team_id'],
} as const;
Loading