Skip to content

Commit 0d2bd96

Browse files
authored
feat: can only edit draft opportunities (#3182)
1 parent f6d8a25 commit 0d2bd96

File tree

3 files changed

+79
-11
lines changed

3 files changed

+79
-11
lines changed

__tests__/schema/opportunity.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ describe('query opportunityById', () => {
125125
opportunityById(id: $id) {
126126
id
127127
type
128+
state
128129
title
129130
tldr
130131
content {
@@ -202,6 +203,7 @@ describe('query opportunityById', () => {
202203
expect(res.data.opportunityById).toEqual({
203204
id: '550e8400-e29b-41d4-a716-446655440001',
204205
type: 1,
206+
state: 2,
205207
title: 'Senior Full Stack Developer',
206208
tldr: 'Join our team as a Senior Full Stack Developer',
207209
content: {
@@ -1782,6 +1784,17 @@ describe('mutation uploadEmploymentAgreement', () => {
17821784
});
17831785

17841786
describe('mutation editOpportunity', () => {
1787+
beforeEach(async () => {
1788+
await con.getRepository(OpportunityJob).update(
1789+
{
1790+
id: opportunitiesFixture[0].id,
1791+
},
1792+
{
1793+
state: OpportunityState.DRAFT,
1794+
},
1795+
);
1796+
});
1797+
17851798
const MUTATION = /* GraphQL */ `
17861799
mutation EditOpportunity($id: ID!, $payload: OpportunityEditInput!) {
17871800
editOpportunity(id: $id, payload: $payload) {
@@ -2232,6 +2245,30 @@ describe('mutation editOpportunity', () => {
22322245

22332246
expect(res.errors).toBeFalsy();
22342247
});
2248+
2249+
it('should throw error when opportunity is not draft', async () => {
2250+
loggedUser = '1';
2251+
2252+
await con
2253+
.getRepository(OpportunityJob)
2254+
.update(
2255+
{ id: opportunitiesFixture[0].id },
2256+
{ state: OpportunityState.LIVE },
2257+
);
2258+
2259+
await testMutationErrorCode(
2260+
client,
2261+
{
2262+
mutation: MUTATION,
2263+
variables: {
2264+
id: opportunitiesFixture[0].id,
2265+
payload: { title: 'Does not matter' },
2266+
},
2267+
},
2268+
'CONFLICT',
2269+
'Only opportunities in draft state can be edited',
2270+
);
2271+
});
22352272
});
22362273

22372274
describe('mutation recommendOpportunityScreeningQuestions', () => {

src/common/opportunity/accessControl.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import { AuthenticationError, ForbiddenError } from 'apollo-server-errors';
22
import type { EntityManager } from 'typeorm';
33
import { OpportunityUser } from '../../entity/opportunities/user';
44
import { OpportunityUserType } from '../../entity/opportunities/types';
5-
import { NotFoundError } from '../../errors';
5+
import { ConflictError, NotFoundError } from '../../errors';
6+
import { Opportunity } from '../../entity/opportunities/Opportunity';
7+
import { OpportunityState } from '@dailydotdev/schema';
68

79
export enum OpportunityPermissions {
810
Edit = 'opportunity_edit',
11+
UpdateState = 'opportunity_update_state',
12+
ViewDraft = 'opportunity_view_draft',
913
}
1014

1115
export const ensureOpportunityPermissions = async ({
@@ -27,16 +31,42 @@ export const ensureOpportunityPermissions = async ({
2731
throw new NotFoundError('Not found!');
2832
}
2933

30-
if ([OpportunityPermissions.Edit].includes(permission)) {
31-
const opportunityUser = await con.getRepository(OpportunityUser).findOne({
32-
where: {
33-
userId,
34-
type: OpportunityUserType.Recruiter,
35-
opportunityId,
36-
},
37-
});
34+
if (
35+
[
36+
OpportunityPermissions.Edit,
37+
OpportunityPermissions.UpdateState,
38+
OpportunityPermissions.ViewDraft,
39+
].includes(permission)
40+
) {
41+
const opportunityUserQb = con
42+
.getRepository(OpportunityUser)
43+
.createQueryBuilder('ou')
44+
.select('ou.type', 'userType')
45+
.where('"userId" = :userId', { userId })
46+
.andWhere('"opportunityId" = :opportunityId', { opportunityId })
47+
.andWhere('ou.type = :type', { type: OpportunityUserType.Recruiter });
48+
49+
if (permission === OpportunityPermissions.Edit) {
50+
opportunityUserQb
51+
.innerJoin(Opportunity, 'o', 'o.id = ou."opportunityId"')
52+
.addSelect('o.state', 'state');
53+
}
54+
55+
const opportunityUser = await opportunityUserQb.getRawOne<{
56+
userType: OpportunityUserType;
57+
state?: OpportunityState;
58+
}>();
3859

3960
if (opportunityUser) {
61+
if (
62+
permission === OpportunityPermissions.Edit &&
63+
opportunityUser.state !== OpportunityState.DRAFT
64+
) {
65+
throw new ConflictError(
66+
'Only opportunities in draft state can be edited',
67+
);
68+
}
69+
4070
return;
4171
}
4272
}

src/schema/opportunity.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export const typeDefs = /* GraphQL */ `
132132
type Opportunity {
133133
id: ID!
134134
type: ProtoEnumValue!
135+
state: ProtoEnumValue!
135136
title: String!
136137
tldr: String
137138
content: OpportunityContent!
@@ -386,7 +387,7 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
386387
con: ctx.con.manager,
387388
userId: ctx.userId,
388389
opportunityId: id,
389-
permission: OpportunityPermissions.Edit,
390+
permission: OpportunityPermissions.ViewDraft,
390391
});
391392
}
392393

@@ -883,7 +884,7 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
883884
con: ctx.con.manager,
884885
userId: ctx.userId,
885886
opportunityId: id,
886-
permission: OpportunityPermissions.Edit,
887+
permission: OpportunityPermissions.UpdateState,
887888
});
888889

889890
const opportunity = await ctx.con

0 commit comments

Comments
 (0)