Skip to content

Commit c351e47

Browse files
authored
Merge pull request #1884 from Northeastern-Electric-Racing/#1542-create-linktype-endpoint
#1542 Create LinkType endpoint
2 parents cef742b + 8c71837 commit c351e47

5 files changed

Lines changed: 102 additions & 1 deletion

File tree

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ export default class ProjectsController {
155155
}
156156
}
157157

158+
static async createLinkType(req: Request, res: Response, next: NextFunction) {
159+
try {
160+
const user: User = await getCurrentUser(res);
161+
const { name, iconName, required } = req.body;
162+
163+
const newLinkType = await ProjectsService.createLinkType(user, name, iconName, required);
164+
res.status(200).json(newLinkType);
165+
} catch (error: unknown) {
166+
next(error);
167+
}
168+
}
169+
158170
static async createAssembly(req: Request, res: Response, next: NextFunction) {
159171
try {
160172
const user: User = await getCurrentUser(res);

src/backend/src/routes/projects.routes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ const projectValidators = [
3232
intMinZero(body('projectManagerId').optional())
3333
];
3434

35+
projectRouter.post(
36+
'/link-types/create',
37+
nonEmptyString(body('name')),
38+
nonEmptyString(body('iconName')),
39+
body('required').isBoolean(),
40+
validateInputs,
41+
ProjectsController.createLinkType
42+
);
43+
3544
projectRouter.post(
3645
'/create',
3746
intMinZero(body('carNumber')),

src/backend/src/services/projects.services.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,39 @@ export default class ProjectsService {
484484
).map(linkTypeTransformer);
485485
}
486486

487+
/**
488+
* Creates a new LinkType with the given information
489+
*
490+
* @param name the name of the new LinkType
491+
* @param iconName the name of the icon for the new LinkType
492+
* @param required is the new LinkType required
493+
* @param user the user who is creating the new LinkType
494+
* @throws AccessDeniedException if the submitter of the request is not an admin
495+
* @throws HttpException if a LinkType of the given name already exists
496+
* @returns the created LinkType
497+
*/
498+
static async createLinkType(user: User, name: string, iconName: string, required: boolean): Promise<LinkType> {
499+
if (!isAdmin(user.role)) throw new AccessDeniedException('Only admins can create link types');
500+
501+
const existingLinkType = await prisma.linkType.findUnique({
502+
where: { name }
503+
});
504+
505+
if (existingLinkType) throw new HttpException(400, 'LinkType with that name already exists');
506+
507+
const linkType = await prisma.linkType.create({
508+
data: {
509+
name,
510+
creatorId: user.userId,
511+
iconName,
512+
required
513+
},
514+
...linkTypeQueryArgs
515+
});
516+
517+
return linkTypeTransformer(linkType);
518+
}
519+
487520
/**
488521
* Creates a new Material
489522
* @param creator the user creating the material

src/backend/tests/projects.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { aquaman, batman, wonderwoman, superman, theVisitor } from './test-data/
55
import {
66
prismaProject1,
77
sharedProject1,
8+
prismaLinkType1,
9+
prismaLinkType2,
810
prismaAssembly1,
911
toolMaterial,
1012
prismaManufacturer1,
@@ -301,6 +303,35 @@ describe('Projects', () => {
301303
});
302304
});
303305

306+
describe('Create LinkType', () => {
307+
test('Create LinkType Type fails for non heads or admins', async () => {
308+
await expect(
309+
ProjectsService.createLinkType(wonderwoman, prismaLinkType1.name, prismaLinkType1.iconName, prismaLinkType1.required)
310+
).rejects.toThrow(new AccessDeniedException('Only admins can create link types'));
311+
});
312+
313+
test('Create LinkType Type fails if LinkType with name already exists', async () => {
314+
vi.spyOn(prisma.linkType, 'findUnique').mockResolvedValue({ ...prismaLinkType2, creatorId: batman.userId });
315+
await expect(
316+
ProjectsService.createLinkType(batman, prismaLinkType1.name, prismaLinkType1.iconName, prismaLinkType1.required)
317+
).rejects.toThrow(new HttpException(400, 'LinkType with that name already exists'));
318+
});
319+
320+
test('Create LinkType successfully returns new LinkType', async () => {
321+
vi.spyOn(prisma.linkType, 'findUnique').mockResolvedValue(null);
322+
vi.spyOn(prisma.linkType, 'create').mockResolvedValue({ ...prismaLinkType2, creatorId: batman.userId });
323+
324+
const linkType = await ProjectsService.createLinkType(
325+
batman,
326+
prismaLinkType2.name,
327+
prismaLinkType2.iconName,
328+
prismaLinkType2.required
329+
);
330+
331+
expect(linkType).toStrictEqual(prismaLinkType2);
332+
});
333+
});
334+
304335
describe('createAssembly', () => {
305336
test('createAssembly fails given invalid project wbs number', async () => {
306337
await expect(

src/backend/tests/test-data/projects.test-data.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
Manufacturer,
99
Unit
1010
} from '@prisma/client';
11-
import { Project as SharedProject, WbsElementStatus } from 'shared';
11+
import { Project as SharedProject, WbsElementStatus, LinkType } from 'shared';
1212
import projectQueryArgs from '../../src/prisma-query-args/projects.query-args';
1313
import { prismaTeam1 } from './teams.test-data';
1414
import { batman, superman } from './users.test-data';
@@ -127,6 +127,22 @@ export const sharedProject1: SharedProject = {
127127
assemblies: []
128128
};
129129

130+
export const prismaLinkType1: LinkType = {
131+
name: 'Confluence',
132+
dateCreated: new Date('01-21-2024'),
133+
creator: batman,
134+
required: true,
135+
iconName: 'ConfluenceIcon'
136+
};
137+
138+
export const prismaLinkType2: LinkType = {
139+
name: 'YouTube',
140+
dateCreated: new Date('01-21-2024'),
141+
creator: batman,
142+
required: true,
143+
iconName: 'YouTubeIcon'
144+
};
145+
130146
export const prismaAssembly1: Assembly = {
131147
name: 'New Assembly',
132148
pdmFileName: 'file.txt',

0 commit comments

Comments
 (0)