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

Feature/homepage old history #3113

Merged
merged 13 commits into from
Jan 10, 2025
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
Loading