Skip to content

Commit 170f4fa

Browse files
authored
Merge pull request #285 from GovStackWorkingGroup/develop
Merge develop with main
2 parents 2071e02 + f7ea219 commit 170f4fa

23 files changed

Lines changed: 482 additions & 86 deletions

File tree

backend/server.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,34 @@ import buildReportRoutes from './src/routes/record';
2020
import buildComplianceRoutes from "./src/routes/compliance";
2121
import { startCronJobs } from "./src/cronJobs";
2222
import buildAuthRoutes from "./src/routes/auth";
23+
import multer from 'multer';
2324

2425
const port: number = parseInt(process.env.PORT as string, 10) || 5000;
25-
const app = express();
26+
const app = express();
2627

2728
createConnection(appConfig).connectToMongo();
2829
app.use(cors());
2930
app.use('d', apiKeyAuth(/^API_KEY_/)); // Matching all process.env.API_KEY_*
3031

3132
app.use(express.json());
33+
3234
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
3335
app.use(buildReportRoutes(reportController(reportRepository, mongoReportRepository)));
3436
app.use(buildComplianceRoutes(complianceController(complianceRepository, mongoComplianceRepository)));
3537
app.use(buildAuthRoutes());
3638

39+
app.use((err: any, _: express.Request, res: express.Response, next: express.NextFunction) => {
40+
if (err instanceof multer.MulterError) {
41+
if (err.code === 'LIMIT_FILE_SIZE') {
42+
return res.status(413).json({ message: 'File too large.' });
43+
}
44+
}
45+
return next(err);
46+
});
47+
3748
app.listen(port, () => {
3849
console.log(`Server is running on port: ${port}`);
3950
startCronJobs();
4051
});
4152

4253
export default app;
43-

backend/src/db/pipelines/compliance/draftDetailAggregation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const draftDetailAggregationPipeline = (draftUuid: string): any[] => {
4646
},
4747
requirementSpecificationCompliance: {
4848
crossCuttingRequirements: { $ifNull: ["$$bbDetail.v.requirementSpecificationCompliance.crossCuttingRequirements", ""] },
49-
functionalRequirements: { $ifNull: ["$$bbDetail.v.functionalRequirements", ""] },
49+
functionalRequirements: { $ifNull: ["$$bbDetail.v.requirementSpecificationCompliance.functionalRequirements", ""] },
5050
keyDigitalFunctionalitiesRequirements: { $ifNull: ["$$bbDetail.v.requirementSpecificationCompliance.keyDigitalFunctionalitiesRequirements", ""] },
5151
},
5252
deploymentCompliance: { $ifNull: ["$$bbDetail.v.deploymentCompliance", ""] }

backend/src/db/pipelines/compliance/softwareDetailAggregation.ts

Lines changed: 220 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,93 @@ export const softwareDetailAggregationPipeline = (softwareName: string): any[] =
22
{
33
$match: { softwareName }
44
},
5+
56
{
6-
$sort: { _id: -1 } // Sort documents by their _id in descending order
7+
$addFields: {
8+
creationDate: { $toDate: "$_id" }
9+
}
10+
},
11+
12+
{
13+
$sort: { creationDate: -1 }
14+
},
15+
16+
// Group all documents and add the latest document as a new field to all documents
17+
{
18+
$group: {
19+
_id: null,
20+
documents: { $push: "$$ROOT" },
21+
latestDocumentRecord: { $first: "$$ROOT" } // Since the documents are sorted, $first will be the latest
22+
}
23+
},
24+
25+
{
26+
$unwind: "$documents"
27+
},
28+
29+
{
30+
$addFields: {
31+
"documents.latestDocumentRecord": "$latestDocumentRecord"
32+
}
33+
},
34+
35+
{
36+
$replaceRoot: { newRoot: "$documents" }
737
},
38+
839
{
940
$project: {
10-
objectId: "$_id", // Use the actual _id field from the document
41+
objectId: "$_id",
1142
softwareName: 1,
1243
compliance: 1,
1344
logo: 1,
1445
website: 1,
1546
documentation: 1,
1647
pointOfContact: 1,
17-
status: 1
48+
status: 1,
49+
creationDate: 1,
50+
latestDocumentRecord: 1
1851
}
1952
},
53+
2054
{
2155
$unwind: { path: "$compliance", preserveNullAndEmptyArrays: true }
2256
},
57+
58+
// Transform the compliance.bbDetails object into an array and add necessary fields
2359
{
2460
$addFields: {
2561
"compliance.bbDetailsArray": {
2662
$map: {
2763
input: { $ifNull: [{ $objectToArray: "$compliance.bbDetails" }, []] },
2864
as: "detail",
2965
in: {
30-
"bbName": "$$detail.k",
31-
"bbVersion": "$$detail.v.bbVersion",
32-
"requirements": "$$detail.v.requirementSpecificationCompliance",
33-
"interface": "$$detail.v.interfaceCompliance",
34-
"deploymentCompliance": "$$detail.v.deploymentCompliance"
66+
bbName: "$$detail.k",
67+
bbVersion: "$$detail.v.bbVersion",
68+
requirements: "$$detail.v.requirementSpecificationCompliance",
69+
interface: "$$detail.v.interfaceCompliance",
70+
deploymentCompliance: "$$detail.v.deploymentCompliance",
71+
creationDate: {
72+
$toDate: {
73+
$convert: {
74+
input: { $substr: ["$$detail.v.bbVersion", 0, 8] },
75+
to: "long",
76+
onError: null,
77+
onNull: null
78+
}
79+
}
80+
}
3581
}
3682
}
3783
}
3884
}
3985
},
86+
4087
{
4188
$unwind: { path: "$compliance.bbDetailsArray", preserveNullAndEmptyArrays: true }
4289
},
90+
91+
// Group by software name, software version, and building block name
4392
{
4493
$group: {
4594
_id: {
@@ -52,17 +101,47 @@ export const softwareDetailAggregationPipeline = (softwareName: string): any[] =
52101
bbVersion: { $ifNull: ["$compliance.bbDetailsArray.bbVersion", "N/A"] },
53102
requirements: { $ifNull: ["$compliance.bbDetailsArray.requirements", {}] },
54103
interface: { $ifNull: ["$compliance.bbDetailsArray.interface", {}] },
55-
deploymentCompliance: { $ifNull: ["$compliance.bbDetailsArray.deploymentCompliance", {}] }
104+
createdDate: "$creationDate",
105+
deploymentCompliance: { $ifNull: ["$compliance.bbDetailsArray.deploymentCompliance", {}] },
106+
documentId: "$objectId" // Capture the _id of the document for this bbVersion
56107
}
57108
},
58-
logo: { $first: "$logo" },
59-
website: { $first: "$website" },
60-
documentation: { $first: "$documentation" },
61-
pointOfContact: { $first: "$pointOfContact" },
62-
status: { $first: "$status" },
63-
objectId: { $first: "$objectId" }
109+
latestRecord: { $first: "$$ROOT" },
110+
latestDocumentRecord: { $first: "$latestDocumentRecord" },
111+
topDocumentId: { $first: "$objectId" } // Store the _id of the first (latest) document
64112
}
65113
},
114+
115+
// Unwind the bbVersions array
116+
{
117+
$unwind: "$bbVersions"
118+
},
119+
120+
// Group by software name, version, and building block name
121+
{
122+
$group: {
123+
_id: {
124+
softwareName: "$_id.softwareName",
125+
softwareVersion: "$_id.softwareVersion",
126+
bbName: "$_id.bbName"
127+
},
128+
bbVersions: {
129+
$push: {
130+
bbVersion: "$bbVersions.bbVersion",
131+
requirements: "$bbVersions.requirements",
132+
interface: "$bbVersions.interface",
133+
deploymentCompliance: "$bbVersions.deploymentCompliance",
134+
creationDate: "$bbVersions.creationDate",
135+
documentId: "$bbVersions.documentId" // Preserve the documentId
136+
}
137+
},
138+
latestRecord: { $first: "$latestRecord" },
139+
latestDocumentRecord: { $first: "$latestDocumentRecord" },
140+
topDocumentId: { $first: "$bbVersions.documentId" } // Capture the documentId of the top bbVersion
141+
}
142+
},
143+
144+
// Group by software name and version, and gather building block details
66145
{
67146
$group: {
68147
_id: {
@@ -72,38 +151,148 @@ export const softwareDetailAggregationPipeline = (softwareName: string): any[] =
72151
bbDetails: {
73152
$push: {
74153
bbName: "$_id.bbName",
75-
bbVersions: "$bbVersions"
154+
bbVersions: "$bbVersions", // This includes the preserved documentId within each bbVersion
155+
topDocumentId: "$topDocumentId" // Capture the topDocumentId for this bbDetail
156+
}
157+
},
158+
latestRecord: { $first: "$latestRecord" },
159+
latestDocumentRecord: { $first: "$latestDocumentRecord" }
160+
}
161+
},
162+
163+
// Project the final set of fields, including maximum creation date
164+
{
165+
$project: {
166+
_id: 1,
167+
bbDetails: {
168+
$map: {
169+
input: "$bbDetails",
170+
as: "detail",
171+
in: {
172+
bbName: "$$detail.bbName",
173+
bbVersions: "$$detail.bbVersions",
174+
topDocumentId: "$$detail.topDocumentId" // Include topDocumentId within each bbDetails entry
175+
}
76176
}
77177
},
78-
logo: { $first: "$logo" },
79-
website: { $first: "$website" },
80-
documentation: { $first: "$documentation" },
81-
pointOfContact: { $first: "$pointOfContact" },
82-
status: { $first: "$status" },
83-
objectId: { $first: "$objectId" }
178+
logo: "$latestRecord.logo",
179+
website: "$latestRecord.website",
180+
documentation: "$latestRecord.documentation",
181+
pointOfContact: "$latestRecord.pointOfContact",
182+
status: "$latestRecord.status",
183+
objectId: "$latestRecord.objectId",
184+
maxCreatedDate: { $max: "$bbDetails.bbVersions.createdDate" },
185+
latestDocumentRecord: 1
84186
}
85187
},
188+
189+
// Group by software name and collect all compliance details
86190
{
87191
$group: {
88192
_id: "$_id.softwareName",
89193
compliance: {
90194
$push: {
91195
softwareVersion: "$_id.softwareVersion",
92196
bbDetails: "$bbDetails",
93-
_id: "$objectId" // Add _id at the same level as softwareVersion
197+
createdDate: "$maxCreatedDate"
94198
}
95199
},
96-
logo: { $first: "$logo" },
97-
website: { $first: "$website" },
98-
documentation: { $first: "$documentation" },
99-
pointOfContact: { $first: "$pointOfContact" },
100-
status: { $first: "$status" },
101-
objectId: { $first: "$objectId" }
200+
logo: { $first: "$latestDocumentRecord.logo" },
201+
website: { $first: "$latestDocumentRecord.website" },
202+
documentation: { $first: "$latestDocumentRecord.documentation" },
203+
pointOfContact: { $first: "$latestDocumentRecord.pointOfContact" },
204+
status: { $first: "$latestDocumentRecord.status" },
205+
objectId: { $first: "$latestDocumentRecord.objectId" },
206+
latestDocumentRecord: { $first: "$latestDocumentRecord" }
207+
}
208+
},
209+
210+
// Add fields to compliance and sort building blocks alphabetically, with "N/A" values at the end
211+
{
212+
$addFields: {
213+
compliance: {
214+
$map: {
215+
input: {
216+
$sortArray: {
217+
input: "$compliance",
218+
sortBy: { softwareVersion: 1 } // Ensure compliance is sorted by softwareVersion in ascending order
219+
}
220+
},
221+
as: "comp",
222+
in: {
223+
softwareVersion: "$$comp.softwareVersion",
224+
bbDetails: {
225+
$let: {
226+
vars: {
227+
detailsWithSortKey: {
228+
$map: {
229+
input: "$$comp.bbDetails",
230+
as: "detail",
231+
in: {
232+
bbName: "$$detail.bbName",
233+
bbVersions: "$$detail.bbVersions",
234+
_id: "$$detail.topDocumentId",
235+
sortKey: {
236+
$cond: {
237+
if: { $eq: ["$$detail.bbName", "N/A"] },
238+
then: 1, // "N/A" values get a higher sortKey to appear at the end
239+
else: 0
240+
}
241+
}
242+
}
243+
}
244+
}
245+
},
246+
in: {
247+
$sortArray: {
248+
input: "$$detailsWithSortKey",
249+
sortBy: { sortKey: 1, bbName: 1 } // Sort alphabetically with "N/A" last
250+
}
251+
}
252+
}
253+
},
254+
_id: "$$comp._id",
255+
createdDate: "$$comp.createdDate"
256+
}
257+
}
258+
}
102259
}
103260
},
261+
262+
{
263+
$sort: { "latestRecord.creationDate": -1 }
264+
},
265+
104266
{
105-
$sort: { _id: -1 } // Sort documents by their _id in descending order
267+
$limit: 1
268+
},
269+
270+
// Extract _id from the top object of bbDetails and include it at the same level as softwareVersion and createdDate
271+
{
272+
$addFields: {
273+
compliance: {
274+
$map: {
275+
input: "$compliance",
276+
as: "comp",
277+
in: {
278+
_id: {
279+
$let: {
280+
vars: {
281+
topDetail: { $arrayElemAt: ["$$comp.bbDetails", 0] } // Get the top object from bbDetails
282+
},
283+
in: "$$topDetail._id" // Extract _id from the top object
284+
}
285+
},
286+
softwareVersion: "$$comp.softwareVersion",
287+
createdDate: "$$comp.createdDate",
288+
bbDetails: "$$comp.bbDetails" // Keep the bbDetails array as is
289+
}
290+
}
291+
}
292+
}
106293
},
294+
295+
// Final projection of the required fields
107296
{
108297
$project: {
109298
_id: "$objectId",
@@ -113,10 +302,7 @@ export const softwareDetailAggregationPipeline = (softwareName: string): any[] =
113302
website: 1,
114303
documentation: 1,
115304
pointOfContact: 1,
116-
status: 1
305+
status: 1,
117306
}
118-
},
119-
{
120-
$limit: 1 // Return the latest matching object
121307
}
122308
];

backend/src/db/repositories/complianceRepository.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ const mongoComplianceRepository: ComplianceDbRepository = {
9898
const isDraft = draftData.status == StatusEnum.DRAFT;
9999
const uniqueId = isDraft ? uuidv4() : '';
100100
const expirationDate = isDraft ? new Date(Date.now() + appConfig.draftExpirationTime) : undefined;
101-
101+
if (draftData.softwareName) {
102+
draftData.softwareName = draftData.softwareName.trim();
103+
}
102104
const newForm = new Compliance({
103105
...draftData,
104106
uniqueId,

0 commit comments

Comments
 (0)