Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d4d4d6e
#3622 basic FSAE parser
cielbellerose Sep 26, 2025
9019844
clean code + lettered bullets create new subrules
cielbellerose Oct 6, 2025
a31c186
#3622 FHE parser beginnings
cielbellerose Oct 15, 2025
79d5fc2
#3622 fhe little img info addition
cielbellerose Nov 3, 2025
d89bfb3
#3622 single parser class with FSAE/FHE inheritance
cielbellerose Nov 3, 2025
3c3bd88
#3622 small fsae formatting fix
cielbellerose Nov 3, 2025
f518513
fsae toc + duplicate temp fix + add to database
cielbellerose Nov 6, 2025
e64fbff
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Nov 19, 2025
d4d0448
#3622 comments added + small updates
cielbellerose Nov 19, 2025
9edeb0f
#3622 basic endpoint setup
cielbellerose Dec 3, 2025
c7c31ea
#3622 parse util functions progress
cielbellerose Dec 3, 2025
07eb8f1
#3622 simplified util functions
cielbellerose Dec 6, 2025
ce707ed
#3622 service update + added pdf parser dependency
cielbellerose Dec 6, 2025
aff6c9b
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Dec 6, 2025
0b07c8e
#3622 remove parser script version
cielbellerose Dec 6, 2025
6c6e283
#3622 frontend connections
cielbellerose Dec 6, 2025
190a3d3
#3622 fix page flow for file upload location
cielbellerose Dec 10, 2025
c468f07
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Dec 14, 2025
cc2155a
#3622 file modal updates
cielbellerose Dec 15, 2025
b578f54
#3622 connect car to file upload modal
cielbellerose Dec 15, 2025
612f3f2
Merge remote-tracking branch 'origin/feature/rules-dashboard' into 36…
cielbellerose Dec 24, 2025
9c85601
#3622 seed updates, route fixes, and temp console logs for file upload
cielbellerose Dec 24, 2025
c1a7dd8
#3622 upload file backend
cielbellerose Dec 24, 2025
d6d82a2
Merge remote-tracking branch 'origin/feature/rules-dashboard' into 36…
cielbellerose Dec 25, 2025
355a09f
#3622 ruleset pr fixes
cielbellerose Jan 2, 2026
935ea9b
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Jan 2, 2026
1defcd9
#3622 added ruleset pr items for ruleset table
cielbellerose Jan 2, 2026
f512d12
#3622 parser adds uploaded ruleset & RULES! view rules on edit page!
cielbellerose Jan 2, 2026
592a425
#3622 fix fsae header/footer & cleaning
cielbellerose Jan 3, 2026
853b148
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Jan 4, 2026
3119d5c
#3622 edit/view are disabled when parsing is happening
cielbellerose Jan 4, 2026
a3f7483
#3622 orphan fixes
cielbellerose Jan 4, 2026
9c3f328
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Jan 4, 2026
a223431
#3622 error handling fix + cleaning
cielbellerose Jan 10, 2026
c87b81f
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Jan 10, 2026
89759da
#3622 linting
cielbellerose Jan 12, 2026
e2a0ba0
#3622 lint
cielbellerose Jan 12, 2026
d010583
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
cielbellerose Jan 15, 2026
dced682
fixed co-pilot recs
Aryan0102 Jan 19, 2026
2d127a9
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
Aryan0102 Jan 19, 2026
e812220
#3622 fixed RulesetEditPage
Zwendle Jan 20, 2026
3ae3fc0
#3622 lint fix
Zwendle Jan 20, 2026
c19732c
#3622 small fixes from copilot
Zwendle Jan 20, 2026
57650c7
Merge branch 'feature/rules-dashboard' into 3622-rulesdashboard-parsing
Aryan0102 Jan 20, 2026
b282f8c
fixed parent references
Aryan0102 Jan 20, 2026
710011e
fix prettier and tsc
Aryan0102 Jan 20, 2026
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
1 change: 1 addition & 0 deletions src/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"jsonwebtoken": "^8.5.1",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.1",
"pdf-parse-new": "^1.4.1",
"prisma": "^6.2.1",
"shared": "1.0.0"
},
Expand Down
43 changes: 43 additions & 0 deletions src/backend/src/controllers/rules.controllers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextFunction, Request, Response } from 'express';
import RulesService from '../services/rules.services';
import { ProjectRule, Rule, Ruleset } from 'shared';
import { HttpException } from '../utils/errors.utils';

export default class RulesController {
static async getActiveRuleset(req: Request, res: Response, next: NextFunction) {
Expand All @@ -13,6 +14,16 @@ export default class RulesController {
}
}

static async getRulesetById(req: Request, res: Response, next: NextFunction) {
try {
const { rulesetId } = req.params;
const ruleset = await RulesService.getRulesetById(rulesetId, req.organization.organizationId);
res.status(200).json(ruleset);
} catch (error: unknown) {
next(error);
}
}

static async createRule(req: Request, res: Response, next: NextFunction) {
try {
const { ruleCode, ruleContent, rulesetId, parentRuleId, referencedRules, imageFileIds } = req.body;
Expand Down Expand Up @@ -274,6 +285,38 @@ export default class RulesController {
}
}

static async parseRuleset(req: Request, res: Response, next: NextFunction) {
try {
const { fileId, parserType } = req.body;
const { rulesetId } = req.params;

const parseResult = await RulesService.parseRuleset(
req.currentUser,
req.organization.organizationId,
fileId,
rulesetId,
parserType
);

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

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

const fileId = await RulesService.uploadRulesetFile(req.file, req.currentUser, req.organization);
res.status(200).json(fileId);
} catch (error: unknown) {
next(error);
}
}
Comment on lines 307 to 318
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uploadRulesetFile controller method lacks JSDoc documentation. Consider adding documentation describing the file upload endpoint, accepted file types, size limits, and the returned fileId format.

Copilot uses AI. Check for mistakes.

static async getSingleRuleset(req: Request, res: Response, next: NextFunction) {
try {
const { rulesetId } = req.params;
Expand Down
15 changes: 15 additions & 0 deletions src/backend/src/prisma-query-args/rules.query-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,18 @@ export const getRulesetQueryArgs = () =>
}
}
});

export const getRulesetPreviewQueryArgs = () =>
Prisma.validator<Prisma.RulesetDefaultArgs>()({
select: {
name: true,
dateCreated: true,
rulesetType: true,
active: true,
car: {
include: {
wbsElement: true
}
}
}
});
10 changes: 9 additions & 1 deletion src/backend/src/prisma/seed-data/rules.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ const rulesetType1 = (userCreatedId: string, organizationId: string): Prisma.Rul
};
};

const rulesetType2 = (userCreatedId: string, organizationId: string): Prisma.Ruleset_TypeCreateInput => {
return {
name: 'FHE',
createdBy: { connect: { userId: userCreatedId } },
organization: { connect: { organizationId } }
};
};

const emptyRulesetType = (userCreatedId: string, organizationId: string): Prisma.Ruleset_TypeCreateInput => {
return {
name: 'Empty Ruleset Type',
Expand Down Expand Up @@ -115,7 +123,6 @@ const projectRule2 = (projectId: string, ruleId: string, createdByUserId: string

export const seedRulesetType = async (submitter: User, name: string, organization: Organization) => {
const createdRulesetType = await RulesService.createRulesetType(submitter, name, organization);

return createdRulesetType;
};

Expand All @@ -125,6 +132,7 @@ export const ruleSeedData = {
thirdLevelRule,
leafRule,
rulesetType1,
rulesetType2,
emptyRulesetType,
ruleset1,
secondActiveRuleset,
Expand Down
130 changes: 37 additions & 93 deletions src/backend/src/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import {
Task_Priority,
Task_Status,
Team,
Part_Tag,
Prisma,
Ruleset_Type
Part_Tag
} from '@prisma/client';
import { createUser, dbSeedAllUsers } from './seed-data/users.seed';
import { dbSeedAllTeams } from './seed-data/teams.seed';
Expand Down Expand Up @@ -51,7 +49,6 @@ import AnnouncementService from '../services/announcement.services';
import OnboardingServices from '../services/onboarding.services';
import { dbSeedAllParts, dbSeedAllPartTags } from './seed-data/parts.seed';
import FinanceServices from '../services/finance.services';
import { getUserQueryArgs } from '../prisma-query-args/user.query-args';
import { ruleSeedData } from './seed-data/rules.seed';
import RulesService from '../services/rules.services';
import { seedRulesetType } from './seed-data/rules.seed';
Expand Down Expand Up @@ -807,16 +804,6 @@ const performSeed: () => Promise<void> = async () => {
ner
);

/**
* Ruleset Types
*/

/** FSAE ruleset type */
const rulesetTypeFSAE = await seedRulesetType(joeShmoe, 'FSAE', ner);

/** FHE ruleset type */
const rulesetTypeFHE = await seedRulesetType(joeBlow, 'FHE', ner);

/**
* Graphs
*/
Expand Down Expand Up @@ -3073,43 +3060,6 @@ const performSeed: () => Promise<void> = async () => {
}
});

/**
* Rules
*/

// ruleset types
const fsaeRulesetType = await prisma.ruleset_Type.create({
data: ruleSeedData.rulesetType1(batman.userId, ner.organizationId)
});

const emptyRulesetType = await prisma.ruleset_Type.create({
data: ruleSeedData.emptyRulesetType(batman.userId, ner.organizationId)
});

// rulesets
const ruleset1 = await prisma.ruleset.create({
data: ruleSeedData.ruleset1(fergus.carId, batman.userId, fsaeRulesetType.rulesetTypeId)
});

const secondActiveRuleset = await prisma.ruleset.create({
data: ruleSeedData.secondActiveRuleset(fergus.carId, batman.userId, fsaeRulesetType.rulesetTypeId)
});

// rules
const ruleT = await prisma.rule.create({ data: ruleSeedData.topLevelRule(ruleset1.rulesetId, batman.userId) });
const ruleT2 = await prisma.rule.create({
data: ruleSeedData.secondLevelRule(ruleset1.rulesetId, batman.userId, ruleT.ruleId)
});
const ruleT21 = await prisma.rule.create({
data: ruleSeedData.thirdLevelRule(ruleset1.rulesetId, batman.userId, ruleT2.ruleId)
});
const ruleT211 = await prisma.rule.create({
data: ruleSeedData.leafRule(ruleset1.rulesetId, batman.userId, ruleT21.ruleId)
});

// project rules
await RulesService.createProjectRule(batman, ner, ruleT211.ruleId, project1Id);

const goldSponsorTier = await FinanceServices.createSponsorTier(thomasEmrax, 'Gold', ner, '#9F9156', 3000);
await FinanceServices.createSponsorTier(thomasEmrax, 'Silver', ner, '#C0C0C0', 200);
await FinanceServices.createSponsorTier(thomasEmrax, 'Bronze', ner, '#CD7F32', 10);
Expand Down Expand Up @@ -3140,18 +3090,31 @@ const performSeed: () => Promise<void> = async () => {
);

/**
* RULESET TYPES AND RULESETS
* Rules
*/

const formulaStudentRulesetType = await prisma.ruleset_Type.create({
data: {
name: 'Formula Student Rules',
createdByUserId: superman.userId,
organizationId: ner.organizationId
}
// ruleset types
const fsaeRulesetType = await prisma.ruleset_Type.create({
data: ruleSeedData.rulesetType1(batman.userId, ner.organizationId)
});

await prisma.ruleset_Type.create({
data: ruleSeedData.rulesetType2(batman.userId, ner.organizationId)
});

await prisma.ruleset_Type.create({
data: ruleSeedData.emptyRulesetType(batman.userId, ner.organizationId)
});

// rulesets
const ruleset1 = await prisma.ruleset.create({
data: ruleSeedData.ruleset1(fergus.carId, batman.userId, fsaeRulesetType.rulesetTypeId)
});

await prisma.ruleset.create({
data: ruleSeedData.secondActiveRuleset(fergus.carId, batman.userId, fsaeRulesetType.rulesetTypeId)
});

// Create rulesets
const fsae2025Ruleset = await prisma.ruleset.create({
data: {
fileId: 'fsae-2025-rules-file-id',
Expand All @@ -3174,9 +3137,21 @@ const performSeed: () => Promise<void> = async () => {
}
});

/**
* RULES
*/
// rules
const ruleT = await prisma.rule.create({ data: ruleSeedData.topLevelRule(ruleset1.rulesetId, batman.userId) });
const ruleT2 = await prisma.rule.create({
data: ruleSeedData.secondLevelRule(ruleset1.rulesetId, batman.userId, ruleT.ruleId)
});
const ruleT21 = await prisma.rule.create({
data: ruleSeedData.thirdLevelRule(ruleset1.rulesetId, batman.userId, ruleT2.ruleId)
});
const ruleT211 = await prisma.rule.create({
data: ruleSeedData.leafRule(ruleset1.rulesetId, batman.userId, ruleT21.ruleId)
});

// project rules
await RulesService.createProjectRule(batman, ner, ruleT211.ruleId, project1Id);

// Technical Rules Section
const techRule = await prisma.rule.create({
data: {
Expand Down Expand Up @@ -3227,13 +3202,6 @@ const performSeed: () => Promise<void> = async () => {
createdByUserId: thomasEmrax.userId
}
});
const rulesetType = await prisma.ruleset_Type.create({
data: {
name: 'FSAE',
createdByUserId: thomasEmrax.userId,
organizationId: ner.organizationId
}
});

// Powertrain Rules
const powertrainRule = await prisma.rule.create({
Expand Down Expand Up @@ -3512,30 +3480,6 @@ const performSeed: () => Promise<void> = async () => {
createdByUserId: thomasEmrax.userId
}
});

const ruleset = await prisma.ruleset.create({
data: {
name: 'FSAE Rules 2025',
fileId: 'fsae-rules-2025',
active: true,
dateCreated: new Date('2025-01-01T10:00:00Z'),
rulesetTypeId: rulesetType.rulesetTypeId,
createdByUserId: thomasEmrax.userId,
carId: fergus.carId
}
});

await prisma.rule.create({
data: {
ruleCode: 'T2.1.1',
ruleContent:
'The vehicle must be open-wheeled and open-cockpit (a formula style body) with four (4) wheels that are not in a straight line.',
imageFileIds: [],
dateCreated: new Date('2025-09-01T10:00:00Z'),
ruleset: { connect: { rulesetId: ruleset.rulesetId } },
createdBy: { connect: { userId: thomasEmrax.userId } }
}
});
};

performSeed()
Expand Down
14 changes: 14 additions & 0 deletions src/backend/src/routes/rules.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import express from 'express';
import RulesController from '../controllers/rules.controllers';
import { nonEmptyString, validateInputs } from '../utils/validation.utils';
import { body } from 'express-validator';
import { MAX_FILE_SIZE } from 'shared';
import multer, { memoryStorage } from 'multer';

const rulesRouter = express.Router();

rulesRouter.get('/rulesetType/:rulesetTypeId/active', RulesController.getActiveRuleset);
rulesRouter.get('/ruleset/:rulesetId', RulesController.getRulesetById);

rulesRouter.post(
'/rule/create',
Expand Down Expand Up @@ -90,4 +93,15 @@ rulesRouter.get('/:ruleId/subrules', RulesController.getChildRules);
rulesRouter.get('/:rulesetId/parentRules', RulesController.getTopLevelRules);
rulesRouter.get('/ruleset/:rulesetId', RulesController.getSingleRuleset);

rulesRouter.post(
'/ruleset/:rulesetId/parse',
nonEmptyString(body('fileId')),
nonEmptyString(body('parserType')), // 'FSAE' or 'FHE'
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parserType validation only checks if it's a non-empty string but doesn't validate that it's specifically 'FSAE' or 'FHE'. Consider adding validation using express-validator's isIn(['FSAE', 'FHE']) to ensure only valid parser types are accepted at the API boundary.

Suggested change
nonEmptyString(body('parserType')), // 'FSAE' or 'FHE'
nonEmptyString(body('parserType')), // 'FSAE' or 'FHE'
body('parserType').isIn(['FSAE', 'FHE']),

Copilot uses AI. Check for mistakes.
validateInputs,
RulesController.parseRuleset
);

const upload = multer({ limits: { fileSize: MAX_FILE_SIZE }, storage: memoryStorage() });
rulesRouter.post('/upload/file', upload.single('file'), RulesController.uploadRulesetFile);

export default rulesRouter;
Loading