Skip to content

Commit dd6381f

Browse files
committed
Verify project date ranges are valid
1 parent 5249bd6 commit dd6381f

File tree

3 files changed

+35
-0
lines changed

3 files changed

+35
-0
lines changed

src/common/exceptions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './exception';
22
export * from './input.exception';
3+
export * from './range.exception';
34
export * from './duplicate.exception';
45
export * from './not-found.exception';
56
export * from './not-implemented.exception';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { InputException } from '~/common';
2+
3+
export class RangeException extends InputException {
4+
constructor(options?: { message?: string; field?: string; cause?: Error }) {
5+
super(options?.message ?? `Invalid range`, options?.field, options?.cause);
6+
}
7+
}

src/components/project/project.service.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { forwardRef, Inject, Injectable } from '@nestjs/common';
22
import { Many } from '@seedcompany/common';
33
import {
4+
CalendarDate,
45
ClientException,
56
CreationFailed,
67
EnhancedResource,
@@ -10,6 +11,8 @@ import {
1011
many,
1112
NotFoundException,
1213
ObjectView,
14+
Range,
15+
RangeException,
1316
ReadAfterCreationFailed,
1417
Role,
1518
SecuredList,
@@ -27,6 +30,7 @@ import {
2730
Logger,
2831
} from '~/core';
2932
import { Transactional } from '~/core/database';
33+
import { AnyChangesOf } from '~/core/database/changes';
3034
import { Privileges } from '../authorization';
3135
import { withoutScope } from '../authorization/dto';
3236
import { BudgetService } from '../budget';
@@ -101,6 +105,7 @@ export class ProjectService {
101105
input: CreateProject,
102106
session: Session,
103107
): Promise<UnsecuredDto<Project>> {
108+
ProjectDateRangeException.throwIfInvalid(input);
104109
if (input.type !== ProjectType.Internship && input.sensitivity) {
105110
throw new InputException(
106111
'Can only set sensitivity on Internship Projects',
@@ -290,6 +295,8 @@ export class ProjectService {
290295
return await this.readOneUnsecured(input.id, session, changeset);
291296
}
292297

298+
ProjectDateRangeException.throwIfInvalid(currentProject, changes);
299+
293300
let updated = currentProject;
294301
if (changedStep) {
295302
await this.workflow.executeTransitionLegacy(
@@ -611,3 +618,23 @@ export class ProjectService {
611618
);
612619
}
613620
}
621+
622+
class ProjectDateRangeException extends RangeException {
623+
static throwIfInvalid(
624+
current: Partial<Pick<UnsecuredDto<Project>, 'mouStart' | 'mouEnd'>>,
625+
changes: AnyChangesOf<Project> = {},
626+
) {
627+
const start =
628+
changes.mouStart !== undefined ? changes.mouStart : current.mouStart;
629+
const end = changes.mouEnd !== undefined ? changes.mouEnd : current.mouEnd;
630+
if (start && end && start > end) {
631+
const field = changes.mouEnd ? 'project.mouEnd' : 'project.mouStart';
632+
throw new ProjectDateRangeException({ start, end }, field);
633+
}
634+
}
635+
636+
constructor(readonly value: Range<CalendarDate>, readonly field: string) {
637+
const message = "Project's MOU start date must be before the MOU end date";
638+
super({ message, field });
639+
}
640+
}

0 commit comments

Comments
 (0)