Skip to content

Commit

Permalink
Merge branch 'feature/rsc' of github.com:cisagov/XFD into RSC-Dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew-Grayson committed Mar 29, 2024
2 parents ed4b802 + 3f7947d commit dbc3b05
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 48 deletions.
103 changes: 103 additions & 0 deletions backend/src/api/assessments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { validateBody, wrapHandler, NotFound, Unauthorized } from './helpers';
import { Assessment, connectToDatabase } from '../models';
import { isUUID } from 'class-validator';

/**
* @swagger
*
* /assessments:
* post:
* description: Save an RSC assessment to the XFD database.
* tags:
* - Assessments
*/
export const createAssessment = wrapHandler(async (event) => {
const body = await validateBody(Assessment, event.body);

await connectToDatabase();

const assessment = Assessment.create(body);
await Assessment.save(assessment);

return {
statusCode: 200,
body: JSON.stringify(assessment)
};
});

/**
* @swagger
*
* /assessments:
* get:
* description: Lists all assessments for the logged-in user.
* tags:
* - Assessments
*/
export const list = wrapHandler(async (event) => {
const userId = event.requestContext.authorizer!.id;

if (!userId) {
return Unauthorized;
}

await connectToDatabase();

const assessments = await Assessment.find({
where: { user: userId }
});

return {
statusCode: 200,
body: JSON.stringify(assessments)
};
});

/**
* @swagger
*
* /assessments/{id}:
* get:
* description: Return user responses and questions organized by category for a specific assessment.
* parameters:
* - in: path
* name: id
* description: Assessment id
* tags:
* - Assessments
*/
export const get = wrapHandler(async (event) => {
const assessmentId = event.pathParameters?.id;

if (!assessmentId || !isUUID(assessmentId)) {
return NotFound;
}

await connectToDatabase();

const assessment = await Assessment.findOne(assessmentId, {
relations: [
'responses',
'responses.question',
'responses.question.category'
]
});

if (!assessment) {
return NotFound;
}

const responsesByCategory = assessment.responses.reduce((acc, response) => {
const categoryName = response.question.category.name;
if (!acc[categoryName]) {
acc[categoryName] = [];
}
acc[categoryName].push(response);
return acc;
}, {});

return {
statusCode: 200,
body: JSON.stringify(responsesByCategory)
};
});
33 changes: 33 additions & 0 deletions backend/src/models/assessment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
BaseEntity,
Column,
CreateDateColumn,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn
} from 'typeorm';
import { Response } from './response';
import { User } from './user';

@Entity()
export class Assessment extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;

@Column()
type: string;

@ManyToOne(() => User, (user) => user.assessments)
user: User;

@OneToMany(() => Response, (response) => response.assessment)
responses: Response[];
}
26 changes: 26 additions & 0 deletions backend/src/models/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
BaseEntity,
Column,
Entity,
OneToMany,
PrimaryGeneratedColumn
} from 'typeorm';
import { Question } from './question';

@Entity()
export class Category extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
name: string;

@Column({ nullable: true })
number: number;

@Column({ nullable: true })
shortName: string;

@OneToMany(() => Question, (question) => question.category)
questions: Question[];
}
44 changes: 27 additions & 17 deletions backend/src/models/connection.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { createConnection, Connection } from 'typeorm';
import {
// Models for the Crossfeed database
ApiKey,
Assessment,
Category,
Cpe,
Cve,
Domain,
Service,
Vulnerability,
Scan,
Organization,
User,
OrganizationTag,
Question,
Resource,
Response,
Role,
SavedSearch,
Scan,
ScanTask,
Service,
User,
Vulnerability,
Webpage,
ApiKey,
SavedSearch,
OrganizationTag,
Cpe,
Cve,

// Models for the Mini Data Lake database
CertScan,
Expand Down Expand Up @@ -110,20 +115,25 @@ const connectDb = async (logging?: boolean) => {
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [
ApiKey,
Assessment,
Category,
Cpe,
Cve,
Domain,
Service,
Vulnerability,
Scan,
Organization,
User,
OrganizationTag,
Question,
Resource,
Response,
Role,
ScanTask,
Webpage,
ApiKey,
SavedSearch,
OrganizationTag
Scan,
ScanTask,
Service,
User,
Vulnerability,
Webpage
],
synchronize: false,
name: 'default',
Expand Down
25 changes: 15 additions & 10 deletions backend/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
export * from './domain';
export * from './cve';
export * from './cpe';
export * from './service';
export * from './api-key';
export * from './assessment';
export * from './category';
export * from './connection';
export * from './vulnerability';
export * from './scan';
export * from './cpe';
export * from './cve';
export * from './domain';
export * from './organization';
export * from './user';
export * from './organization-tag';
export * from './question';
export * from './resource';
export * from './response';
export * from './role';
export * from './saved-search';
export * from './scan';
export * from './scan-task';
export * from './service';
export * from './user';
export * from './vulnerability';
export * from './webpage';
export * from './api-key';
export * from './saved-search';
export * from './organization-tag';
// Mini data lake models
export * from './mini_data_lake/cert_scans';
export * from './mini_data_lake/cidrs';
Expand Down
40 changes: 40 additions & 0 deletions backend/src/models/question.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
BaseEntity,
Column,
Entity,
Index,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn
} from 'typeorm';
import { Category } from './category';
import { Resource } from './resource';
import { Response } from './response';

@Entity()
@Index(['category', 'number'], { unique: true })
export class Question extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
name: string;

@Column()
longForm: string;

@Column()
number: number;

@ManyToMany(() => Resource, (resource) => resource.questions)
@JoinTable()
resources: Resource[];

@ManyToOne(() => Category, (category) => category.questions)
category: Category;

@OneToMany(() => Response, (response) => response.question)
responses: Response[];
}
33 changes: 33 additions & 0 deletions backend/src/models/resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
BaseEntity,
Column,
Entity,
ManyToMany,
ManyToOne,
PrimaryGeneratedColumn
} from 'typeorm';
import { Question } from './question';

@Entity()
export class Resource extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
description: string;

@Column()
name: string;

@Column('varchar', { array: true })
possibleResponses: string[];

@Column()
type: string;

@Column()
url: string;

@ManyToMany(() => Question, (question) => question.resources)
questions: Question[];
}
26 changes: 26 additions & 0 deletions backend/src/models/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
BaseEntity,
Column,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn
} from 'typeorm';
import { Assessment } from './assessment';
import { Question } from './question';

@Entity()
@Index(['assessment', 'question'], { unique: true })
export class Response extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
text: string;

@ManyToOne(() => Assessment, (assessment) => assessment.responses)
assessment: Assessment;

@ManyToOne(() => Question, (question) => question.responses)
question: Question;
}
Loading

0 comments on commit dbc3b05

Please sign in to comment.