Skip to content

Commit ae6d6bf

Browse files
Merge branch 'develop' into feature/gantt-chart-improvements
2 parents ba224b3 + 90e509b commit ae6d6bf

246 files changed

Lines changed: 26175 additions & 5640 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/system-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
push:
66
branches:
77
- main
8+
- multitenancy
89
- develop
910
- feature/**
1011

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
*.env
2929
.agent
3030

31+
# Generated Claude skills (source is src/docs-site/docs/)
32+
.claude/skills/
33+
3134
npm-debug.log*
3235
yarn-debug.log*
3336
yarn-error.log*
@@ -67,4 +70,5 @@ eb-deploy/
6770

6871
# Claude Code Files
6972
CLAUDE.md
70-
.playwright-mcp/
73+
.playwright-mcp/
74+
docs/

package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
"workspaces": [
77
"src/backend",
88
"src/frontend",
9-
"src/shared"
9+
"src/shared",
10+
"src/docs-site"
1011
],
1112
"scripts": {
1213
"prettier-check": "yarn prettier --check .",
1314
"frontend": "yarn workspace frontend start",
1415
"backend": "yarn workspace backend start",
1516
"backend:dev": "yarn workspace backend dev",
16-
"start": "yarn workspace shared build; concurrently --kill-others-on-fail \"yarn backend:dev\" \"yarn frontend\"",
17+
"start": "yarn workspace shared build; concurrently --kill-others-on-fail --hide 2 \"yarn backend:dev\" \"yarn frontend\" \"yarn docs:dev\"",
1718
"prisma:seed": "cd src/backend; npx prisma db seed",
1819
"prisma:reset:force": "yarn workspace shared build; cd src/backend; npx prisma migrate reset --force",
1920
"prisma:reset": "yarn workspace shared build; cd src/backend; npx prisma migrate reset",
@@ -59,7 +60,11 @@
5960
"docker:i": "cd devContainerization && docker compose -f docker-compose.dev.yml exec -T backend sh -c \"yarn install && cd src/backend && npx prisma generate\" && docker compose -f docker-compose.dev.yml exec -T frontend sh -c \"yarn install\"",
6061
"docker:test": "yarn test:setup && cd devContainerization && docker compose -f docker-compose.dev.yml exec backend bash -c \"yarn test:backend\" && docker compose -f docker-compose.dev.yml exec frontend sh -c \"yarn test:frontend\" && yarn test:teardown",
6162
"docker:rebuild": "docker compose -f devContainerization/docker-compose.dev.yml rm -f -s && yarn docker:start",
62-
"db:pull": "node scripts/db-pull.mjs"
63+
"db:pull": "node scripts/db-pull.mjs",
64+
"docs:dev": "yarn workspace finishline-docs docs:dev",
65+
"docs:build": "yarn workspace finishline-docs docs:build",
66+
"docs:serve": "yarn workspace finishline-docs serve",
67+
"skills:sync": "yarn workspace finishline-docs sync-skills"
6368
},
6469
"resolutions": {
6570
"@types/react": "17.0.1",
@@ -153,6 +158,8 @@
153158
"build",
154159
"coverage",
155160
"docs",
161+
"src/docs-site/.docusaurus",
162+
"src/docs-site/build",
156163
"lambda",
157164
"node_modules",
158165
"public",

src/backend/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import express from 'express';
1+
import express, { Router } from 'express';
22
import cors from 'cors';
33
import cookieParser from 'cookie-parser';
44
import { getUserAndOrganization, prodHeaders, requireJwtDev, requireJwtProd } from './src/utils/auth.utils.js';
@@ -16,7 +16,8 @@ import wbsElementTemplatesRouter from './src/routes/wbs-element-templates.routes
1616
import carsRouter from './src/routes/cars.routes.js';
1717
import organizationRouter from './src/routes/organizations.routes.js';
1818
import recruitmentRouter from './src/routes/recruitment.routes.js';
19-
import { slackEvents } from './src/routes/slack.routes.js';
19+
import { getReceiver } from './src/integrations/slack.js';
20+
import './src/routes/slack.routes.js';
2021
import announcementsRouter from './src/routes/announcements.routes.js';
2122
import onboardingRouter from './src/routes/onboarding.routes.js';
2223
import popUpsRouter from './src/routes/pop-up.routes.js';
@@ -25,6 +26,7 @@ import retrospectiveRouter from './src/routes/retrospective.routes.js';
2526
import partsRouter from './src/routes/parts.routes.js';
2627
import financeRouter from './src/routes/finance.routes.js';
2728
import calendarRouter from './src/routes/calendar.routes.js';
29+
import prospectiveSponsorRouter from './src/routes/prospective-sponsor.routes.js';
2830

2931
const app = express();
3032

@@ -61,9 +63,15 @@ const options: cors.CorsOptions = {
6163
allowedHeaders
6264
};
6365

64-
// so we can listen to slack messages
65-
// NOTE: must be done before using json
66-
app.use('/slack', slackEvents.requestListener());
66+
// Mount Slack Bolt receiver BEFORE other middleware to handle raw body parsing
67+
// Bolt's receiver handles its own body parsing and request verification
68+
// The receiver is configured to handle requests at /slack/events
69+
// Only mount if Slack is configured (when SLACK_BOT_TOKEN is set)
70+
const receiver = getReceiver();
71+
if (receiver) {
72+
app.use(receiver.router as unknown as Router);
73+
}
74+
6775
app.get('/health', (_req, res) => {
6876
res.status(200).json({ status: 'healthy' });
6977
});
@@ -103,6 +111,7 @@ app.use('/retrospective', retrospectiveRouter);
103111
app.use('/parts', partsRouter);
104112
app.use('/finance', financeRouter);
105113
app.use('/calendar', calendarRouter);
114+
app.use('/prospective-sponsors', prospectiveSponsorRouter);
106115
app.use('/', (_req, res) => {
107116
res.status(200).json('Welcome to FinishLine');
108117
});

src/backend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
},
1212
"dependencies": {
1313
"@prisma/client": "^6.2.1",
14-
"@slack/events-api": "^3.0.1",
15-
"@slack/web-api": "^7.8.0",
14+
"@slack/bolt": "^3.22.0",
1615
"@types/concat-stream": "^2.0.0",
1716
"@types/cookie-parser": "^1.4.3",
1817
"@types/cors": "^2.8.12",
@@ -23,6 +22,7 @@
2322
"concat-stream": "^2.0.0",
2423
"cookie-parser": "^1.4.5",
2524
"cors": "^2.8.5",
25+
"dayjs": "^1.11.19",
2626
"decimal.js": "^10.4.3",
2727
"dotenv": "^16.0.1",
2828
"express": "^5.0.0",

src/backend/src/controllers/change-requests.controllers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,25 @@ export default class ChangeRequestsController {
140140
}
141141
}
142142

143+
static async createLeadershipChangeRequest(req: Request, res: Response, next: NextFunction) {
144+
try {
145+
const { wbsNum, leadId, managerId } = req.body;
146+
147+
const cr = await ChangeRequestsService.createLeadershipChangeRequest(
148+
req.currentUser,
149+
wbsNum.carNumber,
150+
wbsNum.projectNumber,
151+
wbsNum.workPackageNumber,
152+
leadId,
153+
managerId,
154+
req.organization
155+
);
156+
res.status(200).json(cr);
157+
} catch (error: unknown) {
158+
next(error);
159+
}
160+
}
161+
143162
static async createStandardChangeRequest(req: Request, res: Response, next: NextFunction) {
144163
try {
145164
const { wbsNum, type, what, why, proposedSolutions, projectProposedChanges, workPackageProposedChanges } = req.body;

src/backend/src/controllers/finance.controllers.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,43 @@ export default class FinanceController {
77
const {
88
name,
99
activeStatus,
10+
valueTypes,
1011
sponsorValue,
1112
joinDate,
1213
activeYears,
1314
sponsorTierId,
1415
taxExempt,
15-
sponsorContact,
16+
contactName,
17+
contactEmail,
18+
contactPhone,
19+
contactPosition,
1620
sponsorTasks,
1721
discountCode,
18-
sponsorNotes
22+
sponsorNotes,
23+
stockDescription,
24+
discountDescription
1925
} = req.body;
2026

2127
const sponsor = await FinanceServices.createSponsor(
2228
req.currentUser,
2329
name,
2430
activeStatus,
25-
sponsorValue,
31+
valueTypes,
2632
joinDate,
2733
activeYears,
28-
sponsorTierId,
34+
sponsorTierId || undefined,
2935
taxExempt,
30-
sponsorContact,
36+
contactName,
3137
sponsorTasks,
3238
req.organization,
39+
sponsorValue,
3340
discountCode,
34-
sponsorNotes
41+
sponsorNotes,
42+
contactEmail,
43+
contactPhone,
44+
contactPosition,
45+
stockDescription,
46+
discountDescription
3547
);
3648
res.status(200).json(sponsor);
3749
} catch (error: unknown) {
@@ -73,7 +85,7 @@ export default class FinanceController {
7385
static async editSponsorTask(req: Request, res: Response, next: NextFunction) {
7486
try {
7587
const { sponsorTaskId } = req.params as Record<string, string>;
76-
const { dueDate, notes, notifyDate, assigneeUserId } = req.body;
88+
const { dueDate, notes, notifyDate, assigneeUserId, done } = req.body;
7789

7890
const updatedSponsorTask = await FinanceServices.editSponsorTask(
7991
req.currentUser,
@@ -82,7 +94,8 @@ export default class FinanceController {
8294
dueDate,
8395
notes,
8496
notifyDate,
85-
assigneeUserId
97+
assigneeUserId,
98+
done
8699
);
87100
res.status(200).json(updatedSponsorTask);
88101
} catch (error: unknown) {
@@ -323,15 +336,21 @@ export default class FinanceController {
323336
const {
324337
name,
325338
activeStatus,
339+
valueTypes,
326340
sponsorValue,
327341
joinDate,
328342
activeYears,
329343
sponsorTierId,
330-
sponsorContact,
344+
contactName,
345+
contactEmail,
346+
contactPhone,
347+
contactPosition,
331348
taxExempt,
332349
sponsorTasks,
333350
discountCode,
334-
sponsorNotes
351+
sponsorNotes,
352+
stockDescription,
353+
discountDescription
335354
} = req.body;
336355

337356
const updatedSponsor = await FinanceServices.editSponsor(
@@ -340,15 +359,21 @@ export default class FinanceController {
340359
sponsorId,
341360
name,
342361
activeStatus,
343-
sponsorValue,
362+
valueTypes,
344363
joinDate,
345364
activeYears,
346-
sponsorTierId,
347-
sponsorContact,
365+
sponsorTierId || undefined,
366+
contactName,
348367
taxExempt,
349368
sponsorTasks,
369+
sponsorValue,
350370
discountCode,
351-
sponsorNotes
371+
sponsorNotes,
372+
contactEmail,
373+
contactPhone,
374+
contactPosition,
375+
stockDescription,
376+
discountDescription
352377
);
353378

354379
res.status(200).json(updatedSponsor);
@@ -385,4 +410,14 @@ export default class FinanceController {
385410
next(error);
386411
}
387412
}
413+
414+
static async toggleSponsorTaskDone(req: Request, res: Response, next: NextFunction) {
415+
try {
416+
const { sponsorTaskId } = req.params as Record<string, string>;
417+
const updatedTask = await FinanceServices.toggleSponsorTaskDone(req.currentUser, req.organization, sponsorTaskId);
418+
res.status(200).json(updatedTask);
419+
} catch (error: unknown) {
420+
next(error);
421+
}
422+
}
388423
}

src/backend/src/controllers/projects.controllers.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -207,35 +207,49 @@ export default class ProjectsController {
207207
price,
208208
subtotal,
209209
linkUrl,
210-
notes,
211-
reimbursementRequestId
210+
notes
212211
} = req.body;
213212
const wbsNum = validateWBS(req.params.wbsNum as string);
214213
const material = await BillOfMaterialsService.createMaterial(
215214
req.currentUser,
216215
name,
217216
status,
218217
materialTypeName,
218+
linkUrl,
219+
wbsNum,
220+
req.organization,
219221
manufacturerName,
220222
manufacturerPartNumber,
221223
quantity,
222224
price,
223225
subtotal,
224-
linkUrl,
225-
wbsNum,
226-
req.organization,
227226
notes,
228227
assemblyId,
229-
pdmFileName === '' ? undefined : pdmFileName,
230-
unitName,
231-
reimbursementRequestId
228+
pdmFileName,
229+
unitName
232230
);
233231
res.status(200).json(material);
234232
} catch (error: unknown) {
235233
next(error);
236234
}
237235
}
238236

237+
static async copyMaterialsToProject(req: Request, res: Response, next: NextFunction) {
238+
try {
239+
const { materialIds, destinationWbsNum } = req.body;
240+
241+
const newMaterialIds = await BillOfMaterialsService.copyMaterialsToProject(
242+
req.currentUser,
243+
materialIds,
244+
destinationWbsNum,
245+
req.organization
246+
);
247+
res.status(200).json(newMaterialIds);
248+
} catch (error: unknown) {
249+
next(error);
250+
}
251+
}
252+
239253
static async createManufacturer(req: Request, res: Response, next: NextFunction) {
240254
try {
241255
const { name } = req.body;
@@ -370,27 +384,25 @@ export default class ProjectsController {
370384
price,
371385
subtotal,
372386
linkUrl,
373-
notes,
374-
reimbursementRequestId
387+
notes
375388
} = req.body;
376389
const updatedMaterial = await BillOfMaterialsService.editMaterial(
377390
req.currentUser,
378391
materialId,
379392
name,
380393
status,
381394
materialTypeName,
395+
linkUrl,
396+
req.organization,
382397
manufacturerName,
383398
manufacturerPartNumber,
384399
quantity,
385400
price,
386401
subtotal,
387-
linkUrl,
388-
req.organization,
389402
notes,
390403
unitName,
391404
assemblyId,
392-
pdmFileName === '' ? undefined : pdmFileName,
393-
reimbursementRequestId
405+
pdmFileName
394406
);
395407
res.status(200).json(updatedMaterial);
396408
} catch (error: unknown) {

0 commit comments

Comments
 (0)