Skip to content

Commit f1225fd

Browse files
authored
Merge branch 'develop' into 1620-fix-getProposal-api-error
2 parents 824f72f + 2c76115 commit f1225fd

38 files changed

Lines changed: 1070 additions & 357 deletions

apps/backend/src/datasources/UserDataSource.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface UserDataSource {
7575
): Promise<number>;
7676
update(user: UpdateUserByIdArgs): Promise<User>;
7777
setUserRoles(id: number, roles: number[]): Promise<void>;
78+
removeUserRoles(id: number, roles: number[]): Promise<void>;
7879
setUserNotPlaceholder(id: number): Promise<User | null>;
7980
checkScientistToProposal(
8081
userId: number,

apps/backend/src/datasources/mockups/UserDataSource.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ export class UserDataSourceMock implements UserDataSource {
475475
async setUserRoles(id: number, roles: number[]): Promise<void> {
476476
// Do something here or remove the function.
477477
}
478+
async removeUserRoles(id: number): Promise<void> {
479+
return;
480+
}
478481
async getUserRoles(id: number): Promise<Role[]> {
479482
if (id == dummyUserOfficer.id) {
480483
return [

apps/backend/src/datasources/postgres/CallDataSource.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ export default class PostgresCallDataSource implements CallDataSource {
572572
'call_has_instruments.call_id',
573573
'call.call_id'
574574
)
575-
.join(
575+
.leftJoin(
576576
'instrument_has_scientists',
577577
'instrument_has_scientists.instrument_id',
578578
'call_has_instruments.instrument_id'
@@ -582,8 +582,12 @@ export default class PostgresCallDataSource implements CallDataSource {
582582
'instruments.instrument_id',
583583
'call_has_instruments.instrument_id'
584584
)
585-
.where('instrument_has_scientists.user_id', scientistId)
586-
.orWhere('instruments.manager_user_id', scientistId);
585+
.where(function () {
586+
this.where('instrument_has_scientists.user_id', scientistId).orWhere(
587+
'instruments.manager_user_id',
588+
scientistId
589+
);
590+
});
587591

588592
return records.map(createCallObject);
589593
}

apps/backend/src/datasources/postgres/ExperimentDataSource.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ export default class PostgresExperimentDataSource
577577
//print all arguments
578578

579579
const query = database('experiments')
580-
.select(['*', database.raw('count(*) OVER() AS full_count')])
580+
.select(['experiments.*', database.raw('count(*) OVER() AS full_count')])
581581
.join(
582582
'proposals',
583583
'proposals.proposal_pk',
@@ -587,16 +587,31 @@ export default class PostgresExperimentDataSource
587587

588588
// Add instrument scientist filtering if provided
589589
if (filter?.instrumentScientistUserId) {
590+
const instrumentScientistUserId = filter.instrumentScientistUserId;
591+
590592
query
593+
.leftJoin('instrument_has_scientists', function () {
594+
this.on(
595+
'experiments.instrument_id',
596+
'=',
597+
'instrument_has_scientists.instrument_id'
598+
).andOnVal(
599+
'instrument_has_scientists.user_id',
600+
'=',
601+
instrumentScientistUserId
602+
);
603+
})
591604
.join(
592-
'instrument_has_scientists',
605+
'instruments',
593606
'experiments.instrument_id',
594-
'instrument_has_scientists.instrument_id'
607+
'instruments.instrument_id'
595608
)
596-
.where(
597-
'instrument_has_scientists.user_id',
598-
filter.instrumentScientistUserId
599-
);
609+
.where(function () {
610+
this.whereNotNull('instrument_has_scientists.user_id').orWhere(
611+
'instruments.manager_user_id',
612+
instrumentScientistUserId
613+
);
614+
});
600615
}
601616

602617
return query

apps/backend/src/datasources/postgres/InstrumentDataSource.ts

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default class PostgresInstrumentDataSource
3737
constructor(
3838
@inject(Tokens.FapDataSource) private fapDataSource: FapDataSource
3939
) {}
40+
4041
private createInstrumentObject(instrument: InstrumentRecord) {
4142
return new Instrument(
4243
instrument.instrument_id,
@@ -296,7 +297,22 @@ export default class PostgresInstrumentDataSource
296297
.whereIn('tag_id', tagIds)
297298
.select('i.*');
298299

299-
return instruments.map(this.createInstrumentWithAvailabilityTimeObject);
300+
const managerInstruments = await database<InstrumentRecord>(
301+
'instruments as i'
302+
)
303+
.where('i.manager_user_id', userId)
304+
.select('i.*');
305+
306+
const allInstruments = [...instruments, ...managerInstruments];
307+
const uniqueInstruments = allInstruments.filter(
308+
(inst, index, self) =>
309+
self.findIndex((i) => i.instrument_id === inst.instrument_id) ===
310+
index
311+
);
312+
313+
return uniqueInstruments.map(
314+
this.createInstrumentWithAvailabilityTimeObject
315+
);
300316
} else {
301317
const instruments =
302318
await database<InstrumentRecord>('instruments as i').select('i.*');
@@ -314,11 +330,12 @@ export default class PostgresInstrumentDataSource
314330
'manager_user_id',
315331
])
316332
.from('instruments as i')
317-
.join('instrument_has_scientists as ihs', {
333+
.leftJoin('instrument_has_scientists as ihs', {
318334
'i.instrument_id': 'ihs.instrument_id',
319335
})
320-
.where('ihs.user_id', userId)
321-
.orWhere('i.manager_user_id', userId)
336+
.where(function () {
337+
this.where('ihs.user_id', userId).orWhere('i.manager_user_id', userId);
338+
})
322339
.distinct('i.instrument_id')
323340
.then((instruments: InstrumentRecord[]) => {
324341
const result = instruments.map((instrument) =>
@@ -803,14 +820,20 @@ export default class PostgresInstrumentDataSource
803820
await database
804821
.count({ count: '*' })
805822
.from('instruments as i')
806-
.join('instrument_has_scientists as ihs', {
807-
'i.instrument_id': 'ihs.instrument_id',
823+
.leftJoin('instrument_has_scientists as ihs', function () {
824+
this.on('i.instrument_id', '=', 'ihs.instrument_id').andOnVal(
825+
'ihs.user_id',
826+
'=',
827+
userId
828+
);
808829
})
809-
.where('ihs.user_id', userId)
810830
.where('i.instrument_id', instrumentId)
831+
.andWhere(function () {
832+
this.whereNotNull('ihs.user_id').orWhere('i.manager_user_id', userId);
833+
})
811834
.first();
812835

813-
return result?.count === '1';
836+
return Number(result?.count) >= 1;
814837
}
815838

816839
async hasInstrumentScientistAccess(
@@ -821,19 +844,27 @@ export default class PostgresInstrumentDataSource
821844
return database
822845
.select([database.raw('count(*) OVER() AS count')])
823846
.from('proposals')
824-
.join('instrument_has_scientists', {
825-
'instrument_has_scientists.user_id': scientistId,
826-
})
827847
.join('instrument_has_proposals', {
828848
'instrument_has_proposals.proposal_pk': 'proposals.proposal_pk',
829-
'instrument_has_proposals.instrument_id':
830-
'instrument_has_scientists.instrument_id',
849+
})
850+
.join('instruments', {
851+
'instruments.instrument_id': 'instrument_has_proposals.instrument_id',
852+
})
853+
.leftJoin('instrument_has_scientists', {
854+
'instrument_has_scientists.instrument_id': 'instruments.instrument_id',
855+
'instrument_has_scientists.user_id': scientistId,
831856
})
832857
.where('proposals.proposal_pk', '=', proposalPk)
833-
.where('instrument_has_scientists.instrument_id', '=', instrumentId)
858+
.where('instrument_has_proposals.instrument_id', '=', instrumentId)
859+
.andWhere(function () {
860+
this.where('instrument_has_scientists.user_id', scientistId).orWhere(
861+
'instruments.manager_user_id',
862+
scientistId
863+
);
864+
})
834865
.first()
835-
.then((result: undefined | { count: string }) => {
836-
return result?.count === '1';
866+
.then((result) => {
867+
return Number(result?.count) >= 1;
837868
});
838869
}
839870

apps/backend/src/datasources/postgres/ProposalDataSource.ts

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ProposalDataSource } from '../ProposalDataSource';
2323
import { TagDataSource } from '../TagDataSource';
2424
import { WorkflowDataSource } from '../WorkflowDataSource';
2525
import {
26+
InstrumentFilterInput,
2627
ProposalsFilter,
2728
QuestionFilterInput,
2829
} from './../../resolvers/queries/ProposalsQuery';
@@ -94,6 +95,29 @@ export async function calculateReferenceNumber(
9495
return prefix + paddedSequence;
9596
}
9697

98+
/**
99+
* Resolves instrument IDs from an InstrumentFilterInput.
100+
* Supports both `instrumentIds` and `instrumentId`(deprecated).
101+
*/
102+
export function resolveInstrumentIds(
103+
instrumentFilter?: InstrumentFilterInput
104+
): number[] | undefined {
105+
if (!instrumentFilter) {
106+
return undefined;
107+
}
108+
if (
109+
instrumentFilter.instrumentIds &&
110+
instrumentFilter.instrumentIds.length > 0
111+
) {
112+
return instrumentFilter.instrumentIds;
113+
}
114+
if (instrumentFilter.instrumentId) {
115+
return [instrumentFilter.instrumentId];
116+
}
117+
118+
return undefined;
119+
}
120+
97121
@injectable()
98122
export default class PostgresProposalDataSource implements ProposalDataSource {
99123
constructor(
@@ -497,11 +521,20 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
497521

498522
if (filter?.instrumentFilter?.showMultiInstrumentProposals) {
499523
query.whereRaw('jsonb_array_length(instruments) > 1');
500-
} else if (filter?.instrumentFilter?.instrumentId) {
501-
query.whereRaw(
502-
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
503-
{ instrumentId: filter.instrumentFilter.instrumentId }
524+
} else {
525+
const effectiveInstrumentIds = resolveInstrumentIds(
526+
filter?.instrumentFilter
504527
);
528+
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
529+
query.where(function () {
530+
effectiveInstrumentIds.forEach((id) => {
531+
this.orWhereRaw(
532+
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
533+
{ instrumentId: id }
534+
);
535+
});
536+
});
537+
}
505538
}
506539

507540
if (filter?.proposalStatusId) {
@@ -655,16 +688,19 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
655688
);
656689
}
657690

658-
if (filter?.instrumentFilter?.instrumentId) {
691+
const effectiveInstrumentIds = resolveInstrumentIds(
692+
filter?.instrumentFilter
693+
);
694+
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
659695
query
660696
.leftJoin(
661697
'instrument_has_proposals',
662698
'instrument_has_proposals.proposal_pk',
663699
'proposals.proposal_pk'
664700
)
665-
.where(
701+
.whereIn(
666702
'instrument_has_proposals.instrument_id',
667-
filter.instrumentFilter.instrumentId
703+
effectiveInstrumentIds
668704
);
669705
}
670706

@@ -814,12 +850,21 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
814850

815851
if (filter?.instrumentFilter?.showMultiInstrumentProposals) {
816852
query.whereRaw('jsonb_array_length(instruments) > 1');
817-
} else if (filter?.instrumentFilter?.instrumentId) {
818-
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
819-
query.whereRaw(
820-
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
821-
{ instrumentId: filter.instrumentFilter?.instrumentId }
853+
} else {
854+
const effectiveInstrumentIds = resolveInstrumentIds(
855+
filter?.instrumentFilter
822856
);
857+
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
858+
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
859+
query.where(function () {
860+
effectiveInstrumentIds.forEach((id) => {
861+
this.orWhereRaw(
862+
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
863+
{ instrumentId: id }
864+
);
865+
});
866+
});
867+
}
823868
}
824869

825870
if (filter?.proposalStatusId) {
@@ -1143,14 +1188,15 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
11431188
'ins.instrument_id'
11441189
)
11451190
.modify((query) => {
1146-
const instrumentId = filter?.instrumentFilter?.instrumentId;
1191+
const effectiveInstrumentIds = resolveInstrumentIds(
1192+
filter?.instrumentFilter
1193+
);
11471194

1148-
if (instrumentId && !isNaN(instrumentId)) {
1195+
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
11491196
query.join('instrument_has_proposals as ihp', function () {
1150-
this.on('ihp.proposal_pk', '=', 'proposals.proposal_pk').andOnVal(
1197+
this.on('ihp.proposal_pk', '=', 'proposals.proposal_pk').andOnIn(
11511198
'ihp.instrument_id',
1152-
'=',
1153-
instrumentId
1199+
effectiveInstrumentIds
11541200
);
11551201
});
11561202
}

apps/backend/src/datasources/postgres/UserDataSource.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ export default class PostgresUserDataSource implements UserDataSource {
151151
});
152152
}
153153

154+
async removeUserRoles(id: number): Promise<void> {
155+
return database.transaction(async (trx) => {
156+
await trx<RoleUserRecord>('role_user').where('user_id', id).del();
157+
});
158+
}
159+
154160
async me(id: number): Promise<User | null> {
155161
return database
156162
.select()

apps/backend/src/datasources/stfc/StfcProposalDataSource.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { container, injectable } from 'tsyringe';
22

3+
import { StfcUserDataSource } from './StfcUserDataSource';
34
import { Tokens } from '../../config/Tokens';
45
import { Call } from '../../models/Call';
56
import { Proposal } from '../../models/Proposal';
@@ -13,6 +14,9 @@ import { PaginationSortDirection } from '../../utils/pagination';
1314
import PostgresAdminDataSource from '../postgres/AdminDataSource';
1415
import PostgresCallDataSource from '../postgres/CallDataSource';
1516
import database from '../postgres/database';
17+
import PostgresProposalDataSource, {
18+
resolveInstrumentIds,
19+
} from '../postgres/ProposalDataSource';
1620
import {
1721
CallRecord,
1822
createCallObject,
@@ -24,8 +28,6 @@ import PostgresTagDataSource from '../postgres/TagDataSource';
2428
import PostgresUserDataSource from '../postgres/UserDataSource';
2529
import PostgresWorkflowDataSource from '../postgres/WorkflowDataSource';
2630
import { ProposalsFilter } from './../../resolvers/queries/ProposalsQuery';
27-
import PostgresProposalDataSource from './../postgres/ProposalDataSource';
28-
import { StfcUserDataSource } from './StfcUserDataSource';
2931

3032
const postgresProposalDataSource = new PostgresProposalDataSource(
3133
new PostgresWorkflowDataSource(new PostgresStatusDataSource()),
@@ -163,12 +165,22 @@ export default class StfcProposalDataSource extends PostgresProposalDataSource {
163165
}
164166
if (filter?.instrumentFilter?.showMultiInstrumentProposals) {
165167
query.whereRaw('jsonb_array_length(instruments) > 1');
166-
} else if (filter?.instrumentFilter?.instrumentId) {
167-
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
168-
query.whereRaw(
169-
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
170-
{ instrumentId: filter?.instrumentFilter?.instrumentId }
168+
} else {
169+
const effectiveInstrumentIds = resolveInstrumentIds(
170+
filter?.instrumentFilter
171171
);
172+
173+
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
174+
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
175+
query.where(function () {
176+
effectiveInstrumentIds.forEach((id) => {
177+
this.orWhereRaw(
178+
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
179+
{ instrumentId: id }
180+
);
181+
});
182+
});
183+
}
172184
}
173185

174186
if (filter?.proposalStatusId) {

0 commit comments

Comments
 (0)