Skip to content

Commit

Permalink
Merge branch 'dev' into leaderboard
Browse files Browse the repository at this point in the history
  • Loading branch information
poojakedia authored Sep 20, 2024
2 parents 2aec0fb + f5dfb39 commit 04bf544
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 8 deletions.
4 changes: 4 additions & 0 deletions serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import resume from '@functions/resume';
import resetPassword from '@functions/reset-password';
import forgotPassword from '@functions/forgot-password';
import leaderboard from '@functions/leaderboard';
import points from '@functions/points';


Check failure on line 17 in serverless.ts

View workflow job for this annotation

GitHub Actions / eslint

More than 1 blank line not allowed
import * as path from 'path';
import * as dotenv from 'dotenv';
Expand Down Expand Up @@ -49,6 +51,8 @@ const serverlessConfiguration: AWS = {
forgotPassword,
resetPassword,
leaderboard,
points,

},
package: { individually: true, patterns: ['!.env*', '.env.vault'] },
custom: {
Expand Down
19 changes: 17 additions & 2 deletions src/functions/attend-event/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ const attendEvent: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (ev
$push: { [`day_of.event.${hackEvent}.time`]: currentTime },
}
);
} else if (event.body.again === false) {
// if can only attend this event once and user has already attended
} else if (attendEvent.day_of.event[hackEvent].attend >= event.body.limit) {
// if attended this event the max times allowed as per limit
return {
statusCode: 409,
body: JSON.stringify({
Expand All @@ -97,6 +97,21 @@ const attendEvent: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (ev
);
}

if (event.body.points) {
const points = db.getCollection('f24-points-syst');
const userPoints = await points.findOne({ email: event.body.qr });
if (!userPoints) await points.insertOne({ email: event.body.qr, balance: 0, total_points: 0 });

if (event.body.points < 0)
await points.updateOne({ email: event.body.qr }, { $inc: { balance: event.body.points } });
else if (event.body.points > 0) {
await points.updateOne(
{ email: event.body.qr },
{ $inc: { balance: event.body.points, total_points: event.body.points } }
);
}
}

// return success case
return {
statusCode: 200,
Expand Down
5 changes: 3 additions & 2 deletions src/functions/attend-event/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export default {
auth_token: { type: 'string' },
qr: { type: 'string', format: 'email' },
event: { type: 'string' },
again: { type: 'boolean', default: true },
points: { type: 'number' },
limit: { type: 'number' },
},
required: ['auth_email', 'auth_token', 'qr', 'event'],
required: ['auth_email', 'auth_token', 'qr', 'event', 'limit'],
} as const;
1 change: 1 addition & 0 deletions src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { default as waiver } from './waiver';
export { default as resume } from './resume';
export { default as resetPassword } from './reset-password';
export { default as forgotPassword } from './forgot-password';
export { default as points } from './points';
75 changes: 75 additions & 0 deletions src/functions/points/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';
import schema from './schema';
import { MongoDB, validateToken } from '../../util';
import * as path from 'path';
import * as dotenv from 'dotenv';
dotenv.config({ path: path.resolve(process.cwd(), '.env') });

const points: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
const email = event.body.email.toLowerCase();

try {
// check token
const isValidToken = validateToken(event.body.auth_token, process.env.JWT_SECRET, email);
if (!isValidToken) {
return {
statusCode: 401,
body: JSON.stringify({
statusCode: 401,
message: 'Unauthorized',
}),
};
}

// Connect to DB
const db = MongoDB.getInstance(process.env.MONGO_URI);
await db.connect();
const users = db.getCollection('users');
const pointsCollection = db.getCollection('f24-points-syst');

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

// get users points
const pointUser = await pointsCollection.findOne({ email: email });
if (!pointUser) {
return {
statusCode: 404,
body: JSON.stringify({
statusCode: 404,
message: 'Points not found for this user.',
}),
};
}

return {
statusCode: 200,
body: JSON.stringify({
statusCode: 200,
balance: pointUser.balance,
total_points: pointUser.total_points,
}),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({
statusCode: 500,
message: 'Internal server error.',
error,
}),
};
}
};

export const main = middyfy(points);
20 changes: 20 additions & 0 deletions src/functions/points/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: 'points',
cors: true,
request: {
schemas: {
'application/json': schema,
},
},
},
},
],
};
8 changes: 8 additions & 0 deletions src/functions/points/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
type: 'object',
properties: {
auth_token: { type: 'string' },
email: { type: 'string', format: 'email' },
},
required: ['auth_token', 'email'],
} as const;
8 changes: 4 additions & 4 deletions tests/attend-event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('Attend-Event tests', () => {
auth_token: 'mockToken',
qr: '[email protected]',
event: 'lunch',
again: false,
limit: 1,
};
const path = '/attend-event';
const httpMethod = 'POST';
Expand Down Expand Up @@ -83,12 +83,13 @@ describe('Attend-Event tests', () => {

// case 4
it('user tries to check into an event the second time but it can only be attended once', async () => {
userData.again = false;
findOneMock
.mockReturnValueOnce({
day_of: {
event: {
lunch: 1,
lunch: {
attend: 1,
},
},
},
})
Expand All @@ -113,7 +114,6 @@ describe('Attend-Event tests', () => {

// case 5
it('success check-in to an event', async () => {
userData.again = true;
findOneMock
.mockReturnValueOnce({
day_of: {},
Expand Down
111 changes: 111 additions & 0 deletions tests/points.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { main } from '../src/functions/points/handler';
import { createEvent, mockContext } from './helper';
import * as util from '../src/util';

jest.mock('../src/util', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
MongoDB: {
getInstance: jest.fn().mockReturnValue({
connect: jest.fn(),
disconnect: jest.fn(),
getCollection: jest.fn().mockReturnValue({
findOne: jest.fn(),
}),
}),
},
validateToken: jest.fn(),
}));

describe('Points endpoint', () => {
beforeEach(() => {
jest.clearAllMocks();
process.env.JWT_SECRET = 'test-secret';
});

const path = '/points';
const httpMethod = 'POST';

it('should return 401 for invalid auth token', async () => {
const userData = {
email: '[email protected]',
auth_token: 'invalidToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(false);

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(401);
expect(JSON.parse(result.body).message).toBe('Unauthorized');
});

it('should return 404 if user is not found', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;
findOneMock.mockResolvedValue(null);

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(404);
expect(JSON.parse(result.body).message).toBe('User not found.');
});

it('should return 404 if points not found for user', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('').findOne as jest.Mock;
findOneMock.mockResolvedValueOnce({ email: '[email protected]' }); // user found
findOneMock.mockResolvedValueOnce(null); // points not found

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(404);
expect(JSON.parse(result.body).message).toBe('Points not found for this user.');
});

it('should return 200 with balance and total_points for valid user', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};

const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;

findOneMock.mockResolvedValueOnce({ email: '[email protected]' }); // User exists
findOneMock.mockResolvedValueOnce({ user_email: '[email protected]', balance: 100, total_points: 150 }); // Points found

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(200);
const body = JSON.parse(result.body);
expect(body.balance).toBe(100);
expect(body.total_points).toBe(150);
});

it('should return 500 for internal server error', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;
findOneMock.mockRejectedValue(new Error('Database error'));

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(500);
expect(JSON.parse(result.body).message).toBe('Internal server error.');
});
});

0 comments on commit 04bf544

Please sign in to comment.