Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions serverless.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
132 changes: 132 additions & 0 deletions src/functions/teams/leave/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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. Check is teamlead is real
const teamAuth = await users.findOne({ email: team.leader_email });
if (!teamAuth) {
return {
statusCode: 400,
body: JSON.stringify({ statusCode: 400, message: 'Invalid team lead' }),
};
}

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

// 8. 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;

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

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

// 11. clear team_info and set confirmed_team

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

// 12. 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