Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add hook to handle invite code #625

Merged
merged 11 commits into from
Jan 3, 2024
6 changes: 4 additions & 2 deletions packages/api/src/api/project/contributors/put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ export const put = async (req: Request, res: Response) => {
});
if (errorHandled) return;

const project = await entityManager.findOneOrFail(Project, { id: data.projectId });
if (project.inviteCode !== data.inviteCode) {
let project: Project;
try {
project = await entityManager.findOneOrFail(Project, { inviteCode: data.inviteCode });
} catch {
res.sendStatus(404);
return;
}
Expand Down
37 changes: 13 additions & 24 deletions packages/api/tests/api/project/contributors/put.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { validatePayload } from '../../../../src/utils/validatePayload';

jest.mock('../../../../src/utils/validatePayload');
const validatePayloadMock = getMock(validatePayload);
const validPayload = { projectId: '1', inviteCode: '00000000-0000-0000-0000-000000000000' };
const validPayload = { inviteCode: '00000000-0000-0000-0000-000000000000' };
const mockUser = { id: '1' };

describe('project contributors put endpoint', () => {
Expand All @@ -27,21 +27,6 @@ describe('project contributors put endpoint', () => {
expect(res.send).toHaveBeenCalledWith(mockProject);
});

it('should return 404 if the invite code is invalid', async () => {
validatePayloadMock.mockReturnValueOnce({ errorHandled: false, data: validPayload } as any);
const req = createMockRequest({ user: mockUser, ...validPayload } as any);
const res = createMockResponse();
const mockProject = { inviteCode: 'invalid', contributors: { add: jest.fn() } };
req.entityManager.findOneOrFail.mockResolvedValueOnce(mockProject);

await put(req as any, res as any);

expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, { id: validPayload.projectId });
expect(mockProject.contributors.add).not.toBeCalled();
expect(req.entityManager.persist).not.toBeCalled();
expect(res.sendStatus).toHaveBeenCalledWith(404);
});

it('should return 409 if the user is already a contributor', async () => {
validatePayloadMock.mockReturnValueOnce({ errorHandled: false, data: validPayload } as any);
const req = createMockRequest({ user: mockUser, ...validPayload } as any);
Expand All @@ -52,7 +37,9 @@ describe('project contributors put endpoint', () => {

await put(req as any, res as any);

expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, { id: validPayload.projectId });
expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, {
inviteCode: validPayload.inviteCode,
});
expect(mockProject.contributors.add).not.toBeCalled();
expect(req.entityManager.persist).not.toBeCalled();
expect(res.sendStatus).toHaveBeenCalledWith(409);
Expand All @@ -68,7 +55,9 @@ describe('project contributors put endpoint', () => {

await put(req as any, res as any);

expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, { id: validPayload.projectId });
expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, {
inviteCode: validPayload.inviteCode,
});
expect(mockProject.contributors.add).not.toBeCalled();
expect(req.entityManager.persist).not.toBeCalled();
expect(res.sendStatus).toHaveBeenCalledWith(423);
Expand All @@ -84,7 +73,9 @@ describe('project contributors put endpoint', () => {

await put(req as any, res as any);

expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, { id: validPayload.projectId });
expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, {
inviteCode: validPayload.inviteCode,
});
expect(mockProject.contributors.add).not.toBeCalled();
expect(req.entityManager.persist).not.toBeCalled();
expect(res.sendStatus).toHaveBeenCalledWith(500);
Expand All @@ -105,15 +96,13 @@ describe('project contributors put endpoint', () => {
validatePayloadMock.mockReturnValueOnce({ errorHandled: false, data: validPayload } as any);
const req = createMockRequest({ user: mockUser, ...validPayload } as any);
const res = createMockResponse();
req.entityManager.findOneOrFail.mockResolvedValueOnce({
...validPayload,
inviteCode: '2',
} as any);
req.entityManager.findOneOrFail.mockRejectedValueOnce({ name: 'NotFoundError' });

await put(req as any, res as any);

expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, { id: validPayload.projectId });
expect(req.entityManager.findOneOrFail).toBeCalledWith(Project, {
inviteCode: validPayload.inviteCode,
});
expect(res.sendStatus).toHaveBeenCalledWith(404);
});
});
11 changes: 11 additions & 0 deletions packages/database/seeds/seeders/ExpoJudgingSessionSeeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class ExpoJudgingSessionSeeder extends Seeder {
run = async (em: EntityManager): Promise<void> => {
if (env.primaryUserIsAdmin) {
try {
<<<<<<< Updated upstream
const projects = await em.find(Project, {});
const admin = await em.findOneOrFail(User, { id: '1' });

Expand All @@ -20,6 +21,16 @@ export class ExpoJudgingSessionSeeder extends Seeder {
});
expoJudgingSession.projects.set(projects);

=======
for(let i=0;i<2;i=i+1){
const admin = await em.findOneOrFail(User, { id: '1' });
const expoJudgingSession = new ExpoJudgingSession({
createdBy: ref(admin),
});
const projects = await em.find(Project, {});
expoJudgingSession.projects.set(projects);

>>>>>>> Stashed changes
em.persist(expoJudgingSession);
}
} catch {
Expand Down
1 change: 0 additions & 1 deletion packages/shared/src/schema/project/contributors/put.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { z } from 'zod';

export const put = z.object({
projectId: z.string(),
inviteCode: z.string().uuid(),
});
9 changes: 0 additions & 9 deletions packages/shared/tests/schema/project/contributors/put.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ describe('project contributors put schema', () => {
expect(Schema.project.contributors.put.safeParse(validPut).success).toBe(true);
});

it('does not validate an object with an invalid projectId', () => {
expect(
Schema.project.contributors.put.safeParse({
...validPut,
projectId: 1,
}).success,
).toBe(false);
});

it('does not validate an object with an invalid inviteCode', () => {
expect(
Schema.project.contributors.put.safeParse({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useProjectInviteCodeHandler';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import { useUserStore } from '../../stores/user';
import { openErrorToast, openSuccessToast } from '../utils/CustomToast';
import { useRedirectToAuth } from '../layout/RedirectToAuthModal';

const toasts = {
Success: () =>
openSuccessToast({
title: `Registration successful!`,
description: "You're all set! 🚀",
}),
Invalid: () =>
openErrorToast({
title: 'Invalid invite code',
description: 'Please check your invite code and try again.',
}),
ProjectExists: () =>
openErrorToast({
title: 'Project already exists',
description: 'You already have a project.',
}),
};

const useProjectInviteCodeHandler = () => {
const router = useRouter();
const { triggerRedirect } = useRedirectToAuth();
const { user, doneLoading: userLoaded } = useUserStore();
const { projectInviteCode } = router.query;

useEffect(() => {
if (!userLoaded || !projectInviteCode) return;
if (!user) {
triggerRedirect({ returnTo: `/?projectInviteCode=${projectInviteCode}` });
return;
}
if (user?.project) {
toasts.ProjectExists();
return;
}

void (async () => {
let project;
try {
project = (await axios.put(`/api/project/contributors`, { inviteCode: projectInviteCode }))
.data;
} catch {
toasts.Invalid();
return;
}
toasts.Success();

await useUserStore.getState().fetchUser();
SpencerKaiser marked this conversation as resolved.
Show resolved Hide resolved
void router.push(`/project/${project.id}`);
})();
}, [projectInviteCode, userLoaded, triggerRedirect, user, router]);
};

export { useProjectInviteCodeHandler };
2 changes: 2 additions & 0 deletions packages/web/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { PageContainer } from '../components/layout/PageContainer';
import { Prizes } from '../components/Prizes';
import { usePrizesStore } from '../stores/prizes';
import { ProjectRegistrationCTA } from '../components/ProjectRegistrationCTA';
import { useProjectInviteCodeHandler } from '../components/useProjectInviteCodeHandler';

const Home: NextPage = () => {
useProjectInviteCodeHandler();
const { doneLoading: prizesFetched } = usePrizesStore();
React.useEffect(() => {
const { prizes } = usePrizesStore.getState();
Expand Down
Loading