Skip to content
Merged

Dev #108

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
af96cfa
added an option to read all users
RyamL1221 Mar 5, 2025
d85e225
ran prettier
RyamL1221 Mar 5, 2025
1fafe26
ran eslint
RyamL1221 Mar 5, 2025
ebdc8ba
added default value for all in schema
RyamL1221 Mar 6, 2025
79911d6
ensures hackers can only look up their own information
RyamL1221 Mar 6, 2025
9feaa7a
added an all user test case
RyamL1221 Mar 6, 2025
92860d5
ran prettier and eslint
RyamL1221 Mar 6, 2025
6dc08d1
revised if statement for clarity
RyamL1221 Mar 26, 2025
2a7bc3a
fixed eslint errors and ran prettier
RyamL1221 Apr 13, 2025
2f45042
Added basic email and buy-in input validation w/ unit tests
poojakedia Apr 13, 2025
6f893a3
Add email validation helper
poojakedia Apr 14, 2025
326c596
fix eslint issue
poojakedia Apr 14, 2025
ecd73ec
fix prettier
poojakedia Apr 14, 2025
eda1329
fix eslint issue
poojakedia Apr 14, 2025
ef3e96c
fix conlflicting prettier and eslint changes
poojakedia Apr 14, 2025
04f4d92
Merge pull request #94 from HackRU/read-all
avsomers25 Apr 15, 2025
127f001
fix: change error status, validate point ranges, update unit testing
poojakedia Apr 17, 2025
1429f98
Merge pull request #97 from HackRU/input_validation
avsomers25 Apr 22, 2025
5a74e7a
created index.ts for delete endpoint
RyamL1221 May 6, 2025
61a7321
created schema
RyamL1221 May 6, 2025
6853710
created handler
RyamL1221 May 6, 2025
398dd39
unit test for delete endpoint
RyamL1221 May 6, 2025
f6b6b2e
ran prettier and eslint
RyamL1221 May 6, 2025
2d48115
ran prettier on unit test
RyamL1221 May 7, 2025
12d5764
Merge pull request #99 from HackRU/delete
avsomers25 May 9, 2025
803661f
changed delete schema and added endpoint to serverless configuration
RyamL1221 May 9, 2025
8c3b88c
Merge pull request #100 from HackRU/delete
avsomers25 May 14, 2025
819c3dc
Add user-exists endpoint and unit tests
poojakedia May 23, 2025
e45cd54
Merge pull request #101 from HackRU/user-exists
avsomers25 May 23, 2025
8343d5b
Update config.ts
avsomers25 May 23, 2025
397f810
Added starter code for Siya to finish.
Ayoobf May 30, 2025
6abbc6f
Completed TODO tasks for backend of interest form
sg2113 Jun 7, 2025
c09c634
Merge pull request #103 from HackRU/interest-form-siya
avsomers25 Jun 7, 2025
0b76a97
Ran prettier
Ayoobf Jun 8, 2025
957708d
Added Tests and better validation
Ayoobf Jun 8, 2025
954e53e
Merge pull request #104 from HackRU/interest-form
avsomers25 Jun 8, 2025
ead5147
Removed the 'uri' requirement from the schema.tsx of the interest for…
Ayoobf Jun 24, 2025
82685c1
Ran Prettier
Ayoobf Jun 24, 2025
8e62352
Merge pull request #107 from HackRU/interest-form-hotfix
avsomers25 Jun 24, 2025
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
8 changes: 8 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@
"modifiers": [
"requiresQuotes"
]
},
{
"selector": "objectLiteralProperty",
"filter": {
"regex": "^_id$",
"match": true
},
"format": null
}
]
}
Expand Down
6 changes: 6 additions & 0 deletions serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import updateBuyIns from '@functions/update-buy-ins';
import getBuyIns from '@functions/get-buy-ins';
import notifyByEmail from '@functions/notify-by-email';
import verifyEmail from '@functions/verify-email';
import deleteUser from '@functions/delete';
import userExists from '@functions/user-exists';
import interestForm from '@functions/interest-form';

import * as path from 'path';
import * as dotenv from 'dotenv';
Expand Down Expand Up @@ -60,6 +63,9 @@ const serverlessConfiguration: AWS = {
getBuyIns,
notifyByEmail,
verifyEmail,
deleteUser,
userExists,
interestForm,
},
package: { individually: true, patterns: ['!.env*', '.env.vault'] },
custom: {
Expand Down
4 changes: 2 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const registrationStart = '01/01/25';
export const registrationEnd = '02/03/25';
export const registrationStart = '1/01/25';
export const registrationEnd = '10/06/25';
11 changes: 11 additions & 0 deletions src/functions/create/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import schema from './schema';
import { MongoDB } from '../../util';
import * as config from '../../config';

import { validateEmail } from '../../helper';

import * as path from 'path';
import * as dotenv from 'dotenv';
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
Expand All @@ -25,6 +27,15 @@ const create: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event)
}

const uEmail = event.body.email.toLowerCase();
if (!validateEmail(uEmail)) {
return {
statusCode: 403,
body: JSON.stringify({
statusCode: 403,
message: 'Improper Email format',
}),
};
}
let password = event.body.password;

try {
Expand Down
79 changes: 79 additions & 0 deletions src/functions/delete/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';
import schema from './schema';
import { MongoDB, validateToken, ensureRoles, UserDoc } from '../../util';
import * as path from 'path';
import * as dotenv from 'dotenv';

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

const deleteUser: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
try {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { auth_email, auth_token, user_email } = event.body;

// 1. Validate auth token
const tokenValid = validateToken(auth_token, process.env.JWT_SECRET, auth_email);
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<UserDoc>('users');

// 3. Check target user exists
const target = await users.findOne({ email: user_email });
if (!target) {
return {
statusCode: 404,
body: JSON.stringify({ statusCode: 404, message: 'User not found' }),
};
}

// 4. Verify auth user exists
const authUser = await users.findOne({ email: auth_email });
if (!authUser) {
return {
statusCode: 404,
body: JSON.stringify({ statusCode: 404, message: 'Auth user not found' }),
};
}

// 5. Ensure auth user role
if (!ensureRoles(authUser.role, ['director', 'organizer'])) {
return {
statusCode: 401,
body: JSON.stringify({ statusCode: 401, message: 'Only directors/organizers can call this endpoint.' }),
};
}

// 6. Delete the user
const result = await users.deleteOne({ email: user_email });
if (result.deletedCount !== 1) {
// Shouldn't happen since we checked existence
return {
statusCode: 500,
body: JSON.stringify({ statusCode: 500, message: 'Internal server error' }),
};
}

// 7. Success
return {
statusCode: 200,
body: JSON.stringify({ statusCode: 200, message: `Deleted ${user_email} successfully` }),
};
} catch (error) {
console.error('Error deleting user:', error);
return {
statusCode: 500,
body: JSON.stringify({ statusCode: 500, message: 'Internal server error' }),
};
}
};

export const main = middyfy(deleteUser);
20 changes: 20 additions & 0 deletions src/functions/delete/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: 'delete',
cors: true,
request: {
schemas: {
'application/json': schema,
},
},
},
},
],
};
9 changes: 9 additions & 0 deletions src/functions/delete/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
type: 'object',
properties: {
user_email: { type: 'string', format: 'email' },
auth_email: { type: 'string', format: 'email' },
auth_token: { type: 'string' },
},
required: ['user_email', 'auth_token', 'auth_email'],
} as const;
3 changes: 3 additions & 0 deletions src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ export { default as updateBuyIns } from './update-buy-ins';
export { default as getBuyIns } from './get-buy-ins';
export { default as notifyByEmail } from './notify-by-email';
export { default as verifyEmail } from './verify-email';
export { default as delete } from './delete';
export { default as userExists } from './user-exists';
export { default as interestForm } from './interest-form';
87 changes: 87 additions & 0 deletions src/functions/interest-form/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';
import { MongoDB } from '../../util';
import * as path from 'path';
import * as dotenv from 'dotenv';
dotenv.config({ path: path.resolve(process.cwd(), '.env') });

import schema from './schema';

const submitInterestForm: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
try {
// Destructure all the new fields from event.body
const {
firstName,
lastName,
age,
phoneNumber,
email,
school,
levelOfStudy,
countryOfResidence,
linkedInUrl,
mlh_code_of_conduct,
mlh_privacy_policy,
mlh_terms_and_conditions,
} = event.body;

// Validate LinkedIn URL format if provided
if (linkedInUrl && linkedInUrl.trim() !== '') {
const linkedInRegex = /^https?:\/\/(www\.)?linkedin\.com\/in\/[a-zA-Z0-9-]+\/?$/;
if (!linkedInRegex.test(linkedInUrl.trim())) {
return {
statusCode: 422,
body: JSON.stringify({
statusCode: 422,
message: 'Please provide a valid LinkedIn profile URL (e.g., https://linkedin.com/in/yourname)',
}),
};
}
}

// Connect to database
const db = MongoDB.getInstance(process.env.MONGO_URI);
await db.connect();
const interestFormsCollection = db.getCollection('interest-forms'); // Target collection

// Create the document to insert
const docToInsert = {
firstName,
lastName,
age: age,
phoneNumber,
email,
school,
levelOfStudy,
countryOfResidence,
linkedInUrl,
mlh_code_of_conduct,
mlh_privacy_policy,
mlh_terms_and_conditions,
};

const result = await interestFormsCollection.insertOne(docToInsert);

// Return success
return {
statusCode: 200,
body: JSON.stringify({
message: 'Successful Form Submission',
submissionId: result.insertedId,
}),
};
} catch (error) {
console.error('Error submitting interest form:', error);
return {
statusCode: 500,
body: JSON.stringify({
statusCode: 500,
message: 'Internal Server Error',
error: error.message,
}),
};
}
};

export const main = middyfy(submitInterestForm);
20 changes: 20 additions & 0 deletions src/functions/interest-form/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: 'interest-form',
cors: true,
request: {
schemas: {
'application/json': schema,
},
},
},
},
],
};
30 changes: 30 additions & 0 deletions src/functions/interest-form/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default {
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
age: { type: 'number' },
phoneNumber: { type: 'string' },
email: { type: 'string', format: 'email' },
school: { type: 'string' },
levelOfStudy: { type: 'string' },
countryOfResidence: { type: 'string' },
linkedInUrl: { type: 'string' },
mlh_code_of_conduct: { type: 'boolean' },
mlh_privacy_policy: { type: 'boolean' },
mlh_terms_and_conditions: { type: 'boolean' },
},
required: [
'firstName',
'lastName',
'age',
'phoneNumber',
'email',
'school',
'levelOfStudy',
'countryOfResidence',
'mlh_code_of_conduct',
'mlh_privacy_policy',
'mlh_terms_and_conditions',
],
} as const;
51 changes: 38 additions & 13 deletions src/functions/read/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ const read: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) =>
}

const lookupEmail = event.body.email.toLowerCase();
if (!authUser.role['director'] && !authUser.role['organizer'] && authUser.email !== lookupEmail) {

// Ensures user can only look up their own information
if (
!authUser.role['director'] &&
!authUser.role['organizer'] &&
(authUser.email !== lookupEmail || event.body.all)
) {
return {
statusCode: 403,
body: JSON.stringify({
Expand All @@ -62,22 +68,41 @@ const read: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) =>

// Find the user
// eslint-disable-next-line @typescript-eslint/naming-convention
const lookUpUser = await users.findOne({ email: lookupEmail }, { projection: { password: 0, _id: 0 } }); // exclude password and id
if (!lookUpUser) {
if (!event.body.all) {
const lookUpUser = await users.findOne({ email: lookupEmail }, { projection: { password: 0, _id: 0 } }); // exclude password and id
if (!lookUpUser) {
return {
statusCode: 404,
body: JSON.stringify({
statusCode: 404,
message: 'Look-up user not found.',
}),
};
}

// Return user data
return {
statusCode: 404,
body: JSON.stringify({
statusCode: 200,
body: JSON.stringify(lookUpUser),
};
} else {
const lookUpAllUsers = await users.find({}, { projection: { password: 0, _id: 0 } }).toArray(); // exclude password and id
if (!lookUpAllUsers) {
return {
statusCode: 404,
message: 'Look-up user not found.',
}),
body: JSON.stringify({
statusCode: 404,
message: 'Look-up all users not found.',
}),
};
}

// Return user data
return {
statusCode: 200,
body: JSON.stringify(lookUpAllUsers),
};
}

// Return user data
return {
statusCode: 200,
body: JSON.stringify(lookUpUser),
};
} catch (error) {
console.error('Error reading user:', error);
return {
Expand Down
1 change: 1 addition & 0 deletions src/functions/read/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
auth_email: { type: 'string', format: 'email' },
auth_token: { type: 'string' },
email: { type: 'string', format: 'email' },
all: { type: 'boolean', default: false },
},
required: ['auth_email', 'auth_token', 'email'],
} as const;
Loading