Skip to content

Commit

Permalink
Merge pull request #3113 from Northeastern-Electric-Racing/feature/ho…
Browse files Browse the repository at this point in the history
…mepage-old-history

Feature/homepage old history
  • Loading branch information
Peyton-McKee authored Jan 10, 2025
2 parents cbf9cef + a46fbfe commit 3ebd054
Show file tree
Hide file tree
Showing 121 changed files with 7,762 additions and 777 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@types/react-dom": "17.0.1"
},
"dependencies": {
"@slack/events-api": "^3.0.1",
"mitt": "^3.0.1",
"react-hook-form-persist": "^3.0.0",
"typescript": "^4.1.5"
Expand Down
11 changes: 11 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import workPackageTemplatesRouter from './src/routes/work-package-templates.rout
import carsRouter from './src/routes/cars.routes';
import organizationRouter from './src/routes/organizations.routes';
import recruitmentRouter from './src/routes/recruitment.routes';
import { slackEvents } from './src/routes/slack.routes';
import announcementsRouter from './src/routes/announcements.routes';
import onboardingRouter from './src/routes/onboarding.routes';
import popUpsRouter from './src/routes/pop-up.routes';
import statisticsRouter from './src/routes/statistics.routes';

const app = express();
Expand All @@ -41,6 +45,10 @@ const options: cors.CorsOptions = {
allowedHeaders
};

// so we can listen to slack messages
// NOTE: must be done before using json
app.use('/slack', slackEvents.requestListener());

// so that we can use cookies and json
app.use(cookieParser());
app.use(express.json());
Expand Down Expand Up @@ -69,6 +77,9 @@ app.use('/templates', workPackageTemplatesRouter);
app.use('/cars', carsRouter);
app.use('/organizations', organizationRouter);
app.use('/recruitment', recruitmentRouter);
app.use('/pop-ups', popUpsRouter);
app.use('/announcements', announcementsRouter);
app.use('/onboarding', onboardingRouter);
app.use('/statistics', statisticsRouter);
app.use('/', (_req, res) => {
res.status(200).json('Welcome to FinishLine');
Expand Down
34 changes: 34 additions & 0 deletions src/backend/src/controllers/announcements.controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextFunction, Request, Response } from 'express';
import AnnouncementService from '../services/announcement.service';

export default class AnnouncementController {
static async getUserUnreadAnnouncements(req: Request, res: Response, next: NextFunction) {
try {
const { organization, currentUser } = req;

const unreadAnnouncements = await AnnouncementService.getUserUnreadAnnouncements(
currentUser.userId,
organization.organizationId
);
res.status(200).json(unreadAnnouncements);
} catch (error: unknown) {
next(error);
}
}

static async removeUserAnnouncement(req: Request, res: Response, next: NextFunction) {
try {
const { announcementId } = req.params;
const { organization, currentUser } = req;

const unreadAnnouncements = await AnnouncementService.removeUserAnnouncement(
currentUser.userId,
announcementId,
organization.organizationId
);
res.status(200).json(unreadAnnouncements);
} catch (error: unknown) {
next(error);
}
}
}
21 changes: 21 additions & 0 deletions src/backend/src/controllers/onboarding.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextFunction, Request, Response } from 'express';
import OnboardingServices from '../services/onboarding.services';

export default class OnboardingController {
static async downloadImage(req: Request, res: Response, next: NextFunction) {
try {
const { fileId } = req.params;

const imageData = await OnboardingServices.downloadImage(fileId);

// Set the appropriate headers for the HTTP response
res.setHeader('content-type', String(imageData.type));
res.setHeader('content-length', imageData.buffer.length);

// Send the Buffer as the response body
res.send(imageData.buffer);
} catch (error: unknown) {
return next(error);
}
}
}
75 changes: 75 additions & 0 deletions src/backend/src/controllers/organizations.controllers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextFunction, Request, Response } from 'express';
import OrganizationsService from '../services/organizations.services';
import { HttpException } from '../utils/errors.utils';

export default class OrganizationsController {
static async getCurrentOrganization(req: Request, res: Response, next: NextFunction) {
Expand Down Expand Up @@ -60,4 +61,78 @@ export default class OrganizationsController {
next(error);
}
}

static async setOrganizationFeaturedProjects(req: Request, res: Response, next: NextFunction) {
try {
const { projectIds } = req.body;
const featuredProjects = await OrganizationsService.setFeaturedProjects(projectIds, req.organization, req.currentUser);

res.status(200).json(featuredProjects);
} catch (error: unknown) {
next(error);
}
}

static async setLogoImage(req: Request, res: Response, next: NextFunction) {
try {
if (!req.file) {
throw new HttpException(400, 'Invalid or undefined image data');
}

const updatedOrg = await OrganizationsService.setLogoImage(req.file, req.currentUser, req.organization);

res.status(200).json(updatedOrg);
} catch (error: unknown) {
next(error);
}
}

static async getOrganizationLogoImage(req: Request, res: Response, next: NextFunction) {
try {
const { organization } = req;

const logoImageId = await OrganizationsService.getLogoImage(organization.organizationId);
res.status(200).json(logoImageId);
} catch (error: unknown) {
next(error);
}
}

static async setOrganizationDescription(req: Request, res: Response, next: NextFunction) {
try {
const updatedOrg = await OrganizationsService.setOrganizationDescription(
req.body.description,
req.currentUser,
req.organization
);

res.status(200).json(updatedOrg);
} catch (error: unknown) {
next(error);
}
}

static async getOrganizationFeaturedProjects(req: Request, res: Response, next: NextFunction) {
try {
const featuredProjects = await OrganizationsService.getOrganizationFeaturedProjects(req.organization.organizationId);
res.status(200).json(featuredProjects);
} catch (error: unknown) {
next(error);
}
}

static async setSlackWorkspaceId(req: Request, res: Response, next: NextFunction) {
try {
const { workspaceId } = req.body;

const updatedOrg = await OrganizationsService.setSlackWorkspaceId(
workspaceId,
req.currentUser,
req.organization.organizationId
);
res.status(200).json(updatedOrg);
} catch (error: unknown) {
next(error);
}
}
}
27 changes: 27 additions & 0 deletions src/backend/src/controllers/popUps.controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NextFunction, Request, Response } from 'express';
import { PopUpService } from '../services/pop-up.services';

export default class PopUpsController {
static async getUserUnreadPopUps(req: Request, res: Response, next: NextFunction) {
try {
const { organization, currentUser } = req;

const unreadPopUps = await PopUpService.getUserUnreadPopUps(currentUser.userId, organization.organizationId);
res.status(200).json(unreadPopUps);
} catch (error: unknown) {
next(error);
}
}

static async removeUserPopUps(req: Request, res: Response, next: NextFunction) {
try {
const { popUpId } = req.params;
const { organization, currentUser } = req;

const unreadPopUps = await PopUpService.removeUserPopUp(currentUser.userId, popUpId, organization.organizationId);
res.status(200).json(unreadPopUps);
} catch (error: unknown) {
next(error);
}
}
}
18 changes: 18 additions & 0 deletions src/backend/src/controllers/slack.controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getWorkspaceId } from '../integrations/slack';
import OrganizationsService from '../services/organizations.services';
import SlackServices from '../services/slack.services';

export default class SlackController {
static async processMessageEvent(event: any) {
try {
const organizations = await OrganizationsService.getAllOrganizations();
const nerSlackWorkspaceId = await getWorkspaceId();
const relatedOrganization = organizations.find((org) => org.slackWorkspaceId === nerSlackWorkspaceId);
if (relatedOrganization) {
SlackServices.processMessageSent(event, relatedOrganization.organizationId);
}
} catch (error: unknown) {
console.log(error);
}
}
}
24 changes: 24 additions & 0 deletions src/backend/src/controllers/users.controllers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextFunction, Request, Response } from 'express';
import UsersService from '../services/users.services';
import { AccessDeniedException } from '../utils/errors.utils';
import { Task } from 'shared';

export default class UsersController {
static async getAllUsers(_req: Request, res: Response, next: NextFunction) {
Expand Down Expand Up @@ -167,4 +168,27 @@ export default class UsersController {
next(error);
}
}

static async getUserTasks(req: Request, res: Response, next: NextFunction) {
try {
const { userId } = req.params;
const { organization } = req;

const userTasks = await UsersService.getUserTasks(userId, organization);
res.status(200).json(userTasks);
} catch (error: unknown) {
next(error);
}
}

static async getManyUserTasks(req: Request, res: Response, next: NextFunction) {
try {
const { userIds } = req.body;

const tasks: Task[] = await UsersService.getManyUserTasks(userIds, req.organization);
res.status(200).json(tasks);
} catch (error: unknown) {
next(error);
}
}
}
75 changes: 75 additions & 0 deletions src/backend/src/integrations/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,79 @@ const generateSlackTextBlock = (message: string, link?: string, linkButtonText?:
};
};

/**
* Given an id of a channel, produces the slack ids of all the users in that channel.
* @param channelId the id of the channel
* @returns an array of strings of all the slack ids of the users in the given channel
*/
export const getUsersInChannel = async (channelId: string) => {
let members: string[] = [];
let cursor: string | undefined;

try {
do {
const response = await slack.conversations.members({
channel: channelId,
cursor,
limit: 200
});

if (response.ok && response.members) {
members = members.concat(response.members);
cursor = response.response_metadata?.next_cursor;
} else {
throw new Error(`Failed to fetch members: ${response.error}`);
}
} while (cursor);

return members;
} catch (error) {
return members;
}
};

/**
* Given a slack channel id, produces the name of the channel
* @param channelId the id of the slack channel
* @returns the name of the channel or undefined if it cannot be found
*/
export const getChannelName = async (channelId: string) => {
try {
const channelRes = await slack.conversations.info({ channel: channelId });
return channelRes.channel?.name;
} catch (error) {
return undefined;
}
};

/**
* Given a slack user id, prood.uces the name of the channel
* @param userId the id of the slack user
* @returns the name of the user (real name if no display name), undefined if cannot be found
*/
export const getUserName = async (userId: string) => {
try {
const userRes = await slack.users.info({ user: userId });
return userRes.user?.profile?.display_name || userRes.user?.real_name;
} catch (error) {
return undefined;
}
};

/**
* Get the workspace id of the workspace this slack api is registered with
* @returns the id of the workspace
*/
export const getWorkspaceId = async () => {
try {
const response = await slack.auth.test();
if (response.ok) {
return response.team_id;
}
throw new Error(response.error);
} catch (error) {
throw new HttpException(500, 'Error getting slack workspace id: ' + (error as any).data.error);
}
};

export default slack;
11 changes: 11 additions & 0 deletions src/backend/src/prisma-query-args/announcements.query.args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Prisma } from '@prisma/client';
import { getUserQueryArgs } from './user.query-args';

export type AnnouncementQueryArgs = ReturnType<typeof getAnnouncementQueryArgs>;

export const getAnnouncementQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.AnnouncementDefaultArgs>()({
include: {
usersReceived: getUserQueryArgs(organizationId)
}
});
7 changes: 5 additions & 2 deletions src/backend/src/prisma-query-args/auth-user.query-args.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Prisma } from '@prisma/client';
import { getTeamQueryArgs } from './teams.query-args';

export type AuthUserQueryArgs = ReturnType<typeof getAuthUserQueryArgs>;

Expand All @@ -9,13 +10,15 @@ export const getAuthUserQueryArgs = (organizationId: string) =>
teamsAsHead: {
where: {
organizationId
}
},
...getTeamQueryArgs(organizationId)
},
organizations: true,
teamsAsLead: {
where: {
organizationId
}
},
...getTeamQueryArgs(organizationId)
},
teamsAsMember: {
where: {
Expand Down
11 changes: 11 additions & 0 deletions src/backend/src/prisma-query-args/pop-up.query-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Prisma } from '@prisma/client';
import { getUserQueryArgs } from './user.query-args';

export type PopUpQueryArgs = ReturnType<typeof getPopUpQueryArgs>;

export const getPopUpQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.PopUpDefaultArgs>()({
include: {
usersReceived: getUserQueryArgs(organizationId)
}
});

This file was deleted.

Loading

0 comments on commit 3ebd054

Please sign in to comment.