Skip to content

Commit

Permalink
Event Service (#475)
Browse files Browse the repository at this point in the history
* Initialize db

* Update .gitignore to ignore local env

* Add dockerfile for db setup

* Add nx commands for db setup

* Change docker container name, add trigram index for event name

* Implement api endpoints

* Attempt to implement calendar widget

Only the outline, hardcoded values

* Add calendar event display and correct margins

* Implement events page styling

* Interface with backend, implement admin functionality

* Implement event tagging

- Updated PUT /events endpoint to also update tags
- Scroll overflow for calendar

* Add prod env vars

* Disable admin enpoints temporarily

Until auth service is developed

* Refector to Chalice

* Fix project structure to allow Chalice to deploy

* Add Nx command for serving locally through Chalice

* Fix error when installing dependencies for event service

* Refactor to use UUID instead of serial

* Fix missing semicolons in sql files

* Update nx deploy command to work with chalice

* Outline event service mobile list

* Add styling for event service mobile list

* Use cockroachdb

* Connect event calendar to API

* Show events in mobile in calendar

* Fix mobile view transition bugs, show pinned events with toggle

* Uncomment admin endpoints, add RBAC

* Implement API auth on frontend

* Add delete event endpoint, fix add event endpoint when undefined params

* Fix leaderboard and other battlepas API (move to nextjs api routes)

* Redesign, cherry pick from Adam's branch

* Update .env.example to include events api

* Update package.json
  • Loading branch information
VictiniX888 authored Nov 3, 2023
1 parent 9c28eee commit 1605307
Show file tree
Hide file tree
Showing 48 changed files with 4,869 additions and 1,355 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ NEXT_PUBLIC_DISABLE_SSO=true to disable, anything else for enable
HIBISCUS_FEATURE_FLAG_REDIS_URL=redis://<username>:<password>@<host>:<port>
HIBISCUS_FEATURE_FLAG_MONGO_URI=PLACEHOLDER

NEXT_PUBLIC_HIBISCUS_EVENTS_API_URL=PLACEHOLDER

NEXT_PUBLIC_TALLY_APPS_2023_X=https://tally.so/embed/<formID>

NEXT_PUBLIC_DISCORD_API_URL=PLACEHOLDER

NEXT_PUBLIC_WAIVER_URL=PLACEHOLDER
NEXT_PUBLIC_HACKER_PACKET_URL=PLACEHOLDER
NEXT_PUBLIC_HACKER_PACKET_URL=PLACEHOLDER
82 changes: 31 additions & 51 deletions apps/dashboard/common/apis/battlepass/battlepass.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { faker } from '@faker-js/faker';
import { HibiscusSupabaseClient } from '@hibiscus/hibiscus-supabase-client';
import { SupabaseClient } from '@supabase/supabase-js';
import { BonusPointsStatus } from './types';
import { container } from 'tsyringe';
import { BattlePassRepository } from 'apps/dashboard/repository/battlepass.repository';
import axios from 'axios';

const getNumberStatusBonusPoint = (status: BonusPointsStatus) => {
switch (status) {
Expand Down Expand Up @@ -34,11 +37,11 @@ export interface BattlepassAPIInterface {
page_number: number;
page_count: number;
leaderboard: {
user_id: string;
first_name: string;
last_name: string;
bonus_points: number;
event_points: number;
total_points: number;
}[];
};
}>;
Expand Down Expand Up @@ -77,11 +80,11 @@ export class BattlepassAPI implements BattlepassAPIInterface {
page_number: number;
page_count: number;
leaderboard: {
user_id: string;
first_name: string;
last_name: string;
bonus_points: number;
event_points: number;
total_points: number;
}[];
};
}> {
Expand All @@ -92,11 +95,11 @@ export class BattlepassAPI implements BattlepassAPIInterface {
page_count: pageSize,
leaderboard: Array.from(Array(pageSize).keys())
.map(() => ({
user_id: faker.datatype.uuid(),
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
bonus_points: faker.datatype.number(),
event_points: faker.datatype.number(),
total_points: faker.datatype.number(),
}))
.sort(
(a, b) =>
Expand All @@ -107,46 +110,30 @@ export class BattlepassAPI implements BattlepassAPIInterface {
},
};
}
const res = await this.client.from('leaderboard').select(
`user_profiles(user_id,first_name,last_name),
bonus_points, event_points`
);
if (!res.data) res.data = [];
const leaderboard = res.data
.sort((a, b) => {
return (
b.bonus_points + b.event_points - (a.bonus_points + a.event_points)
);
})
.slice(pageNum * pageSize - pageSize, pageNum * pageSize);
const returnData = {
data: {
page_number: pageNum,
page_count: pageSize,
leaderboard: leaderboard.map((item) => {
const userProfile = item.user_profiles as any;
return {
user_id: userProfile.user_id,
bonus_points: item.bonus_points,
event_points: item.event_points,
first_name: userProfile.first_name,
last_name: userProfile.last_name,
};
}),
},
};
return returnData;

try {
const res = await axios.get(
`/api/battlepass/leaderboard?pageNumber=${pageNum}&pageSize=${pageSize}`
);

return res.data;
} catch {
throw new Error('Failed to fetch leaderboard');
}
}

async getUserRankLeaderboard(
userId: string
): Promise<{ data: { place: number } }> {
if (this.mock) return { data: { place: faker.datatype.number() } };
const leaderboardRes = await this.getLeaderboard(10000, 1);
const userFoundIndex = leaderboardRes.data.leaderboard.findIndex(
(user) => user.user_id == userId
);
return { data: { place: userFoundIndex + 1 } };

try {
const res = await axios.get(`/api/battlepass/user-rank/${userId}`);

return res.data;
} catch {
throw new Error('Failed to get user rank');
}
}

async getBonusPointEventsUserStatus(userId: string): Promise<{
Expand Down Expand Up @@ -266,20 +253,13 @@ export class BattlepassAPI implements BattlepassAPIInterface {
data: { points: faker.datatype.number() },
};
}
const userLeaderboardRes = await this.client
.from('leaderboard')
.select('bonus_points,event_points')
.eq('user_id', userId)
.single();
if (userLeaderboardRes.error) {
throw userLeaderboardRes.error;

try {
const res = await axios.get(`/api/battlepass/user-points/${userId}`);

return res.data;
} catch {
throw new Error('Failed to get user rank');
}
return {
data: {
points:
userLeaderboardRes.data.bonus_points +
userLeaderboardRes.data.event_points,
},
};
}
}
209 changes: 209 additions & 0 deletions apps/dashboard/common/events.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { getEnv } from '@hibiscus/env';
import axios, { AxiosError } from 'axios';

// Type definitions

export type Event = {
eventId: string;
eventName: string;
startTime: Date;
endTime: Date;
location: string;
description?: string;
eventTags?: string[];
industryTags?: string[];
bpPoints: number;
};

export class EventServiceError extends Error {
constructor(message?: string) {
super(message ?? 'Unknown error occured');
}

static handleAxiosError(e: AxiosError): EventServiceError {
if (
e.response &&
typeof e.response.data === 'object' &&
'error' in e.response.data &&
typeof e.response.data.error === 'string'
) {
return new EventServiceError(e.response.data.error);
} else if (e.response) {
return new EventServiceError('Unknown API error occured');
} else if (e.request) {
return new EventServiceError(
'The request was made but no response was received'
);
} else {
return new EventServiceError(e.message);
}
}
}

// Util functions
export function isSameDate(date1: Date, date2: Date): boolean {
return (
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate()
);
}

export function getDayDate(d: Date): Date {
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}

// Event service API client
export async function getEvent(
eventId: string,
accessToken: string
): Promise<Event> {
const apiUrl = getEnv().Hibiscus.Events.ApiUrl;

try {
const res = await axios(`${apiUrl}/events/${eventId}`, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` },
});
const event = res.data;
event.startTime = new Date(event.startTime);
event.endTime = new Date(event.endTime);
return event;
} catch (e) {
if (axios.isAxiosError(e)) {
throw EventServiceError.handleAxiosError(e);
} else {
throw new EventServiceError();
}
}
}

export async function getAllEvents(accessToken: string): Promise<Event[]> {
const apiUrl = getEnv().Hibiscus.Events.ApiUrl;

// Display all events after Sep 1 2023
const startDate = new Date(2023, 8, 1).toISOString();

try {
const res = await axios(`${apiUrl}/events?after=${startDate}&pageSize=-1`, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` },
});
const events = res.data.events;
for (const e of events) {
e.startTime = new Date(e.startTime);
e.endTime = new Date(e.endTime);
}
return events;
} catch (e) {
if (axios.isAxiosError(e)) {
throw EventServiceError.handleAxiosError(e);
} else {
throw new EventServiceError();
}
}
}

export async function getPinnedEvents(
userId: string,
accessToken: string
): Promise<Event[]> {
const apiUrl = getEnv().Hibiscus.Events.ApiUrl;

try {
const res = await axios(`${apiUrl}/events/pinned-events/${userId}`, {
method: 'GET',
headers: { Authorization: `Bearer ${accessToken}` },
});
const events = res.data.pinnedEvents;
for (const e of events) {
e.startTime = new Date(e.startTime);
e.endTime = new Date(e.endTime);
}
return events;
} catch (e) {
if (axios.isAxiosError(e)) {
throw EventServiceError.handleAxiosError(e);
} else {
throw new EventServiceError();
}
}
}

export async function pinEvent(
userId: string,
eventId: string,
accessToken: string
): Promise<number> {
const apiUrl = getEnv().Hibiscus.Events.ApiUrl;

try {
const res = await axios(`${apiUrl}/events/pinned-events/${userId}`, {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
data: {
pin_event: eventId,
},
});
const pinned = res.data.pinned_event;
return pinned;
} catch (e) {
if (axios.isAxiosError(e)) {
throw EventServiceError.handleAxiosError(e);
} else {
throw new EventServiceError();
}
}
}

export async function unpinEvent(
userId: string,
eventId: string,
accessToken: string
): Promise<number> {
const apiUrl = getEnv().Hibiscus.Events.ApiUrl;

try {
const res = await axios(`${apiUrl}/events/pinned-events/${userId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${accessToken}` },
data: {
unpin_event: eventId,
},
});
const unpinned = res.data.unpinned_event;
return unpinned;
} catch (e) {
if (axios.isAxiosError(e)) {
throw EventServiceError.handleAxiosError(e);
} else {
throw new EventServiceError();
}
}
}

export async function updateEvent(
eventId: string,
props: Partial<Event>,
accessToken: string
): Promise<Event> {
const apiUrl = getEnv().Hibiscus.Events.ApiUrl;

try {
const res = await axios(`${apiUrl}/events/${eventId}`, {
method: 'PUT',
headers: { Authorization: `Bearer ${accessToken}` },
data: {
...props,
},
});
const event = res.data;
return event;
} catch (e) {
if (axios.isAxiosError(e)) {
throw EventServiceError.handleAxiosError(e);
} else {
throw new EventServiceError();
}
}
}
Loading

4 comments on commit 1605307

@vercel
Copy link

@vercel vercel bot commented on 1605307 Nov 3, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

pointr – ./

pointr-git-main-hacksc.vercel.app
pointr-hacksc.vercel.app
pointr.vercel.app
www.hack.sc

@vercel
Copy link

@vercel vercel bot commented on 1605307 Nov 3, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

recruitment – ./

recruitment-git-main-hacksc.vercel.app
recruitment-hacksc.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 1605307 Nov 3, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

www-2023 – ./

www-2023-hacksc.vercel.app
2023.hacksc.com
www-2023-git-main-hacksc.vercel.app
www-2023.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 1605307 Nov 3, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.