Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import calendarRouter from './src/routes/calendar.routes.js';
import prospectiveSponsorRouter from './src/routes/prospective-sponsor.routes.js';
import attendanceRouter from './src/routes/attendance.routes.js';
import icsRouter from './src/routes/ics.routes.js';
import dashboardsRouter from './src/routes/dashboards.routes.js';

const app = express();

Expand Down Expand Up @@ -122,6 +123,7 @@ app.use('/finance', financeRouter);
app.use('/calendar', calendarRouter);
app.use('/prospective-sponsors', prospectiveSponsorRouter);
app.use('/attendance', attendanceRouter);
app.use('/dashboards', dashboardsRouter);
app.use('/', (_req, res) => {
res.status(200).json('Welcome to FinishLine');
});
Expand Down
52 changes: 52 additions & 0 deletions src/backend/src/controllers/dashboards.controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { NextFunction, Request, Response } from 'express';
import DashboardService from '../services/dashboards.services.js';

export default class DashboardsController {
static async createDashboard(req: Request, res: Response, next: NextFunction) {
try {
const { name, link } = req.body;
const { currentUser, organization } = req;

const dashboard = await DashboardService.createDashboard(currentUser, organization, name, link);
res.status(200).json(dashboard);
} catch (error: unknown) {
next(error);
}
}

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

const dashboards = await DashboardService.getUserDashboards(currentUser, organization);
res.status(200).json(dashboards);
} catch (error: unknown) {
next(error);
}
}

static async editDashboard(req: Request, res: Response, next: NextFunction) {
try {
const { dashboardId } = req.params as Record<string, string>;
const { link } = req.body;
const { currentUser, organization } = req;

const dashboard = await DashboardService.editDashboard(currentUser, organization, dashboardId, link);
res.status(200).json(dashboard);
} catch (error: unknown) {
next(error);
}
}

static async deleteDashboard(req: Request, res: Response, next: NextFunction) {
try {
const { dashboardId } = req.params as Record<string, string>;
const { currentUser, organization } = req;

const dashboard = await DashboardService.deleteDashboard(currentUser, organization, dashboardId);
res.status(200).json(dashboard);
} catch (error: unknown) {
next(error);
}
}
}
9 changes: 9 additions & 0 deletions src/backend/src/controllers/projects.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ export default class ProjectsController {
}
}

static async getAllProjectsDropdown(req: Request, res: Response, next: NextFunction) {
try {
const projects = await ProjectsService.getAllProjectsDropdown(req.organization);
res.status(200).json(projects);
} catch (error: unknown) {
next(error);
}
}

static async getUsersTeamsProjects(req: Request, res: Response, next: NextFunction) {
try {
const projects: ProjectOverview[] = await ProjectsService.getUsersTeamsProjects(
Expand Down
21 changes: 19 additions & 2 deletions src/backend/src/controllers/tasks.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,19 @@ export default class TasksController {

static async getFilteredTasks(req: Request, res: Response, next: NextFunction) {
try {
const { memberIds, teamIds, startPeriod, endPeriod, labelIds, wbsNum } = req.body;
const {
memberIds,
teamIds,
startPeriod,
endPeriod,
labelIds,
wbsNum,
carNumbers,
projectWbsNums,
workPackageWbsNums,
search,
andMemberTeam
} = req.body;

const tasks = await TasksService.getFilteredTasks(
{
Expand All @@ -108,7 +120,12 @@ export default class TasksController {
startPeriod: startPeriod ? new Date(startPeriod) : undefined,
endPeriod: endPeriod ? new Date(endPeriod) : undefined,
labelIds,
wbsNum
wbsNum,
carNumbers,
projectWbsNums,
workPackageWbsNums,
search,
andMemberTeam
},
req.organization
);
Expand Down
10 changes: 10 additions & 0 deletions src/backend/src/controllers/teams.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ export default class TeamsController {
}
}

static async getAllTeamsDropdown(req: Request, res: Response, next: NextFunction) {
try {
const teams = await TeamsService.getAllTeamsDropdown(req.organization);

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

static async getAllArchivedTeams(req: Request, res: Response, next: NextFunction) {
try {
const teams = await TeamsService.getAllArchivedTeams(req.organization);
Expand Down
9 changes: 9 additions & 0 deletions src/backend/src/controllers/users.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ export default class UsersController {
}
}

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

static async getCurrentUser(req: Request, res: Response, next: NextFunction) {
try {
const user = await UsersService.getCurrentUser(req.currentUser);
Expand Down
10 changes: 10 additions & 0 deletions src/backend/src/controllers/work-packages.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ export default class WorkPackagesController {
}
}

// Fetch a minimal list of work packages for dropdowns (id + name + wbsNum + projectName)
static async getAllWorkPackagesDropdown(req: Request, res: Response, next: NextFunction) {
try {
const workPackages = await WorkPackagesService.getAllWorkPackagesDropdown(req.organization);
res.status(200).json(workPackages);
} catch (error: unknown) {
next(error);
}
}

// Fetch the work package for the specified WBS number
static async getSingleWorkPackage(req: Request, res: Response, next: NextFunction) {
try {
Expand Down
8 changes: 8 additions & 0 deletions src/backend/src/prisma-query-args/dashboards.query-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Prisma } from '@prisma/client';

export type DashboardQueryArgs = ReturnType<typeof getDashboardQueryArgs>;

export const getDashboardQueryArgs = (_organizationId: string) =>
Prisma.validator<Prisma.DashboardDefaultArgs>()({
include: {}
});
47 changes: 47 additions & 0 deletions src/backend/src/prisma-query-args/dropdown.query-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* This file is part of NER's FinishLine and licensed under GNU AGPLv3.
* See the LICENSE file in the repository root folder for details.
*/

import { Prisma } from '@prisma/client';

/**
* Minimal query args backing the `/dropdown` endpoints. Each selects only the id, display name, and
* just enough context to render/disambiguate the item in a dropdown.
*/

export type ProjectDropdownQueryArgs = ReturnType<typeof getProjectDropdownQueryArgs>;
export type WorkPackageDropdownQueryArgs = ReturnType<typeof getWorkPackageDropdownQueryArgs>;
export type MemberDropdownQueryArgs = ReturnType<typeof getMemberDropdownQueryArgs>;
export type TeamDropdownQueryArgs = ReturnType<typeof getTeamDropdownQueryArgs>;

export const getProjectDropdownQueryArgs = () =>
Prisma.validator<Prisma.ProjectDefaultArgs>()({
select: {
projectId: true,
wbsElement: {
select: { name: true, carNumber: true, projectNumber: true, workPackageNumber: true }
}
}
});

export const getWorkPackageDropdownQueryArgs = () =>
Prisma.validator<Prisma.Work_PackageDefaultArgs>()({
select: {
workPackageId: true,
wbsElement: {
select: { name: true, carNumber: true, projectNumber: true, workPackageNumber: true }
},
project: { select: { wbsElement: { select: { name: true } } } }
}
});

export const getMemberDropdownQueryArgs = () =>
Prisma.validator<Prisma.UserDefaultArgs>()({
select: { userId: true, firstName: true, lastName: true, email: true }
});

export const getTeamDropdownQueryArgs = () =>
Prisma.validator<Prisma.TeamDefaultArgs>()({
select: { teamId: true, teamName: true }
});
4 changes: 2 additions & 2 deletions src/backend/src/prisma-query-args/projects.query-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const getProjectQueryArgs = (organizationId: string) =>
lead: getUserQueryArgs(organizationId),
manager: getUserQueryArgs(organizationId),
descriptionBullets: { where: { dateDeleted: null }, ...getDescriptionBulletQueryArgs(organizationId) },
tasks: { where: { dateDeleted: null }, ...getTaskQueryArgs(organizationId) },
tasks: { where: { dateDeleted: null }, ...getTaskQueryArgs() },
links: { where: { dateDeleted: null }, ...getLinkQueryArgs() },
changes: {
where: { changeRequest: { dateDeleted: null } },
Expand Down Expand Up @@ -55,7 +55,7 @@ export const getProjectGanttQueryArgs = (organizationId: string) =>
where: {
dateDeleted: null
},
...getTaskQueryArgs(organizationId)
...getTaskQueryArgs()
}
}
},
Expand Down
19 changes: 9 additions & 10 deletions src/backend/src/prisma-query-args/tasks.query-args.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Prisma } from '@prisma/client';
import { getUserQueryArgs } from './user.query-args.js';
import { getUserPreviewWithEmailQueryArgs, getUserQueryArgs } from './user.query-args.js';

export type TaskQueryArgs = ReturnType<typeof getTaskQueryArgs>;
export type TaskPreviewQueryArgs = ReturnType<typeof getTaskPreviewQueryArgs>;
Expand Down Expand Up @@ -33,7 +33,7 @@ export const getBlockingWorkPackagesArgs = () =>
Prisma.validator<Prisma.WBS_ElementDefaultArgs>()({
include: {
workPackage: {
include: {
select: {
blockedBy: {
select: {
carNumber: true,
Expand All @@ -48,19 +48,19 @@ export const getBlockingWorkPackagesArgs = () =>
}
});

export const getTaskQueryArgs = (organizationId: string) =>
export const getTaskQueryArgs = () =>
Prisma.validator<Prisma.TaskDefaultArgs>()({
include: {
wbsElement: getBlockingWorkPackagesArgs(),
createdBy: getUserQueryArgs(organizationId),
deletedBy: getUserQueryArgs(organizationId),
assignees: getUserQueryArgs(organizationId),
createdBy: getUserPreviewWithEmailQueryArgs(),
deletedBy: getUserPreviewWithEmailQueryArgs(),
assignees: getUserPreviewWithEmailQueryArgs(),
labels: getTaskLabelQueryArgs(),
blockedBy: getTaskBlockedByQueryArgs()
}
});

export const getCalendarTaskQueryArgs = (organizationId: string) =>
export const getCalendarTaskQueryArgs = () =>
Prisma.validator<Prisma.TaskDefaultArgs>()({
include: {
wbsElement: {
Expand All @@ -77,9 +77,8 @@ export const getCalendarTaskQueryArgs = (organizationId: string) =>
workPackage: getBlockingWorkPackagesArgs().include.workPackage
}
},
createdBy: getUserQueryArgs(organizationId),
deletedBy: getUserQueryArgs(organizationId),
assignees: getUserQueryArgs(organizationId),
createdBy: getUserPreviewWithEmailQueryArgs(),
assignees: getUserPreviewWithEmailQueryArgs(),
labels: getTaskLabelQueryArgs(),
blockedBy: getTaskBlockedByQueryArgs()
}
Expand Down
12 changes: 12 additions & 0 deletions src/backend/src/prisma-query-args/user.query-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Prisma } from '@prisma/client';

export type UserQueryArgs = ReturnType<typeof getUserQueryArgs>;

export type UserPreviewWithEmailQueryArgs = ReturnType<typeof getUserPreviewWithEmailQueryArgs>;

export type UserWithSettingsQueryArgs = ReturnType<typeof getUserWithSettingsQueryArgs>;

export type UserScheduleSettingsQueryArgs = ReturnType<typeof getUserScheduleSettingsQueryArgs>;
Expand All @@ -27,6 +29,16 @@ export const getUserPreviewQueryArgs = () =>
}
});

export const getUserPreviewWithEmailQueryArgs = () =>
Prisma.validator<Prisma.UserDefaultArgs>()({
select: {
userId: true,
firstName: true,
lastName: true,
email: true
}
});

export const getUserWithSettingsQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.UserDefaultArgs>()({
include: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "Dashboard" (
"dashboardId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"link" TEXT NOT NULL,
"dateCreated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"dateDeleted" TIMESTAMP(3),
"userId" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,

CONSTRAINT "Dashboard_pkey" PRIMARY KEY ("dashboardId")
);

-- CreateIndex
CREATE INDEX "Dashboard_organizationId_idx" ON "Dashboard"("organizationId");

-- CreateIndex
CREATE INDEX "Dashboard_userId_idx" ON "Dashboard"("userId");

-- AddForeignKey
ALTER TABLE "Dashboard" ADD CONSTRAINT "Dashboard_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Dashboard" ADD CONSTRAINT "Dashboard_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("organizationId") ON DELETE RESTRICT ON UPDATE CASCADE;
17 changes: 17 additions & 0 deletions src/backend/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ model User {
attendedMeetingAttendances Meeting_Attendance[] @relation(name: "meetingAttendees")
createdTaskLabels Task_Label[] @relation(name: "taskLabelCreator")
deletedTaskLabels Task_Label[] @relation(name: "taskLabelDeleter")
dashboards Dashboard[] @relation(name: "userDashboards")
}

model Role {
Expand Down Expand Up @@ -1415,6 +1416,22 @@ model Organization {
eventTypes Event_Type[]
meetingAttendances Meeting_Attendance[]
taskLabels Task_Label[]
dashboards Dashboard[]
}

model Dashboard {
dashboardId String @id @default(uuid())
name String
link String
dateCreated DateTime @default(now())
dateDeleted DateTime?
user User @relation(fields: [userId], references: [userId], name: "userDashboards")
userId String
organization Organization @relation(fields: [organizationId], references: [organizationId])
organizationId String

@@index([organizationId])
@@index([userId])
}

model FrequentlyAskedQuestion {
Expand Down
Loading
Loading