Skip to content

Commit 8fb57e9

Browse files
authoredMar 27, 2025
[FEATURE] Maddo - Récupération des campagnes d'une organisation (PIX-17223).
#11860
2 parents 35baa4d + fd8011d commit 8fb57e9

File tree

9 files changed

+224
-3
lines changed

9 files changed

+224
-3
lines changed
 

‎api/db/seeds/data/common/organization-builder.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ async function _createScoOrganization(databaseBuilder) {
3737
{ id: FEATURE_COMPUTE_ORGANIZATION_LEARNER_CERTIFICABILITY_ID },
3838
{ id: FEATURE_MULTIPLE_SENDING_ASSESSMENT_ID },
3939
],
40+
tagIds: [COLLEGE_TAG.id],
4041
});
4142

4243
await organization.createOrganization({
@@ -52,7 +53,6 @@ async function _createScoOrganization(databaseBuilder) {
5253
{ id: FEATURE_MULTIPLE_SENDING_ASSESSMENT_ID },
5354
{ id: FEATURE_ATTESTATIONS_MANAGEMENT_ID, params: JSON.stringify([ATTESTATIONS.SIXTH_GRADE]) },
5455
],
55-
tagIds: [COLLEGE_TAG.id],
5656
});
5757

5858
await organization.createOrganization({
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1+
import boom from '@hapi/boom';
2+
13
import { usecases } from '../domain/usecases/index.js';
24

35
export async function getOrganizations(request, h, dependencies = { findOrganizations: usecases.findOrganizations }) {
46
const organizations = await dependencies.findOrganizations({ organizationIds: request.pre.organizationIds });
57
return h.response(organizations).code(200);
68
}
9+
10+
export async function getOrganizationCampaigns(request, h, dependencies = { findCampaigns: usecases.findCampaigns }) {
11+
const organizationIds = request.pre.organizationIds;
12+
const requestedOrganizationId = request.params.organizationId;
13+
if (!organizationIds.includes(requestedOrganizationId)) {
14+
return boom.forbidden();
15+
}
16+
const campaigns = await dependencies.findCampaigns({ organizationId: requestedOrganizationId });
17+
return h.response(campaigns).code(200);
18+
}

‎api/src/maddo/application/organizations-routes.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { getOrganizations } from './organizations-controller.js';
1+
import Joi from 'joi';
2+
3+
import { identifiersType } from '../../shared/domain/types/identifiers-type.js';
4+
import { getOrganizationCampaigns, getOrganizations } from './organizations-controller.js';
25
import { organizationPreHandler } from './pre-handlers.js';
36

47
const register = async function (server) {
@@ -10,7 +13,23 @@ const register = async function (server) {
1013
auth: { access: { scope: 'meta' } },
1114
pre: [organizationPreHandler],
1215
handler: getOrganizations,
13-
notes: ["- Retourne la liste des organizations auxquelles l'application client a droit"],
16+
notes: ["- Retourne la liste des organisations auxquelles l'application client a droit"],
17+
tags: ['api', 'meta'],
18+
},
19+
},
20+
{
21+
method: 'GET',
22+
path: '/api/organizations/{organizationId}/campaigns',
23+
config: {
24+
auth: { access: { scope: 'meta' } },
25+
validate: {
26+
params: Joi.object({
27+
organizationId: identifiersType.organizationId,
28+
}),
29+
},
30+
pre: [organizationPreHandler],
31+
handler: getOrganizationCampaigns,
32+
notes: ["- Retourne la liste des campaignes de l'organisation fournie"],
1433
tags: ['api', 'meta'],
1534
},
1635
},
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export class Campaign {
2+
constructor({
3+
id,
4+
name,
5+
organizationId,
6+
organizationName,
7+
type,
8+
targetProfileId,
9+
targetProfileName,
10+
code,
11+
createdAt,
12+
}) {
13+
this.id = id;
14+
this.name = name;
15+
this.organizationId = organizationId;
16+
this.organizationName = organizationName;
17+
this.type = type;
18+
this.targetProfileId = targetProfileId;
19+
this.targetProfileName = targetProfileName;
20+
this.code = code;
21+
this.createdAt = createdAt;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export async function findCampaigns({ organizationId, campaignRepository }) {
2+
return campaignRepository.findByOrganizationId(organizationId);
3+
}

‎api/src/maddo/domain/usecases/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
33

44
import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js';
55
import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js';
6+
import * as campaignRepository from '../../infrastructure/repositories/campaign-repository.js';
67
import * as clientApplicationRepository from '../../infrastructure/repositories/client-application-repository.js';
78
import * as organizationRepository from '../../infrastructure/repositories/organization-repository.js';
89

@@ -11,6 +12,7 @@ const path = dirname(fileURLToPath(import.meta.url));
1112
const dependencies = {
1213
clientApplicationRepository,
1314
organizationRepository,
15+
campaignRepository,
1416
};
1517

1618
const usecasesWithoutInjectedDependencies = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { knex } from '../../../../db/knex-database-connection.js';
2+
import { Campaign } from '../../domain/models/Campaign.js';
3+
4+
export async function findByOrganizationId(organizationId) {
5+
const rawCampaigns = await knex
6+
.select(
7+
'campaigns.id',
8+
'campaigns.organizationId',
9+
'campaigns.name',
10+
'campaigns.type',
11+
'campaigns.targetProfileId',
12+
'campaigns.code',
13+
'campaigns.createdAt',
14+
'target-profiles.name as targetProfileName',
15+
'organizations.name as organizationName',
16+
)
17+
.from('campaigns')
18+
.join('organizations', 'campaigns.organizationId', 'organizations.id')
19+
.join('target-profiles', 'campaigns.targetProfileId', 'target-profiles.id')
20+
.where('campaigns.organizationId', organizationId)
21+
.orderBy('campaigns.id');
22+
return rawCampaigns.map(toDomain);
23+
}
24+
25+
function toDomain(rawCampaign) {
26+
return new Campaign(rawCampaign);
27+
}

‎api/tests/maddo/application/acceptance/organizations-routes_test.js

+82
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Campaign } from '../../../../src/maddo/domain/models/Campaign.js';
12
import { Organization } from '../../../../src/maddo/domain/models/Organization.js';
23
import {
34
createMaddoServer,
@@ -51,4 +52,85 @@ describe('Acceptance | Maddo | Route | Organizations', function () {
5152
]);
5253
});
5354
});
55+
56+
describe('GET /api/organizations/{organizationId}/campaigns', function () {
57+
let orgaInJurisdiction, orgaAlsoInJurisdiction, orgaNotInJurisdiction;
58+
let clientId;
59+
60+
beforeEach(async function () {
61+
orgaInJurisdiction = databaseBuilder.factory.buildOrganization({ name: 'orga-in-jurisdiction' });
62+
orgaAlsoInJurisdiction = databaseBuilder.factory.buildOrganization({ name: 'orga-also-in-jurisdiction' });
63+
orgaNotInJurisdiction = databaseBuilder.factory.buildOrganization({ name: 'orga-not-in-jurisdiction' });
64+
65+
const tag = databaseBuilder.factory.buildTag();
66+
databaseBuilder.factory.buildOrganizationTag({ organizationId: orgaInJurisdiction.id, tagId: tag.id });
67+
databaseBuilder.factory.buildOrganizationTag({ organizationId: orgaAlsoInJurisdiction.id, tagId: tag.id });
68+
69+
clientId = 'client';
70+
databaseBuilder.factory.buildClientApplication({
71+
clientId: 'client',
72+
jurisdiction: { rules: [{ name: 'tags', value: [tag.name] }] },
73+
});
74+
await databaseBuilder.commit();
75+
});
76+
77+
it('returns the list of all campaigns belonging to organization in the client jurisdiction with an HTTP status code 200', async function () {
78+
// given
79+
const targetProfile = databaseBuilder.factory.buildTargetProfile();
80+
const campaign1InJurisdiction = databaseBuilder.factory.buildCampaign({
81+
organizationId: orgaInJurisdiction.id,
82+
targetProfileId: targetProfile.id,
83+
});
84+
databaseBuilder.factory.buildCampaign({
85+
organizationId: orgaAlsoInJurisdiction.id,
86+
});
87+
databaseBuilder.factory.buildCampaign({ organizationId: orgaNotInJurisdiction.id });
88+
89+
await databaseBuilder.commit();
90+
91+
const options = {
92+
method: 'GET',
93+
url: `/api/organizations/${orgaInJurisdiction.id}/campaigns`,
94+
headers: {
95+
authorization: generateValidRequestAuthorizationHeaderForApplication(clientId, 'pix-client', 'meta'),
96+
},
97+
};
98+
99+
// when
100+
const response = await server.inject(options);
101+
102+
// then
103+
expect(response.statusCode).to.equal(200);
104+
expect(response.result).to.deep.equal([
105+
new Campaign({
106+
id: campaign1InJurisdiction.id,
107+
name: campaign1InJurisdiction.name,
108+
organizationId: orgaInJurisdiction.id,
109+
organizationName: orgaInJurisdiction.name,
110+
type: campaign1InJurisdiction.type,
111+
targetProfileId: targetProfile.id,
112+
targetProfileName: targetProfile.name,
113+
code: campaign1InJurisdiction.code,
114+
createdAt: campaign1InJurisdiction.createdAt,
115+
}),
116+
]);
117+
});
118+
119+
it('responds with an HTTP Forbidden when organization is not in jurisdiction', async function () {
120+
// given
121+
const options = {
122+
method: 'GET',
123+
url: `/api/organizations/${orgaNotInJurisdiction.id}/campaigns`,
124+
headers: {
125+
authorization: generateValidRequestAuthorizationHeaderForApplication(clientId, 'pix-client', 'meta'),
126+
},
127+
};
128+
129+
// when
130+
const response = await server.inject(options);
131+
132+
// then
133+
expect(response.statusCode).to.equal(403);
134+
});
135+
});
54136
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Campaign } from '../../../../../src/maddo/domain/models/Campaign.js';
2+
import { findByOrganizationId } from '../../../../../src/maddo/infrastructure/repositories/campaign-repository.js';
3+
import { databaseBuilder, expect } from '../../../../test-helper.js';
4+
5+
describe('Maddo | Infrastructure | Repositories | Integration | campaign', function () {
6+
describe('#findByOrganizationId', function () {
7+
it('lists campaigns belonging to organization with given id', async function () {
8+
// given
9+
const organization = databaseBuilder.factory.buildOrganization();
10+
const { id: otherOrganizationId } = databaseBuilder.factory.buildOrganization();
11+
const targetProfile = databaseBuilder.factory.buildTargetProfile();
12+
const campaign1 = databaseBuilder.factory.buildCampaign({
13+
organizationId: organization.id,
14+
targetProfileId: targetProfile.id,
15+
});
16+
const campaign2 = databaseBuilder.factory.buildCampaign({
17+
organizationId: organization.id,
18+
targetProfileId: targetProfile.id,
19+
});
20+
databaseBuilder.factory.buildCampaign({ organizationId: otherOrganizationId });
21+
await databaseBuilder.commit();
22+
23+
// when
24+
const campaigns = await findByOrganizationId(organization.id);
25+
26+
// then
27+
expect(campaigns).to.deep.equal([
28+
new Campaign({
29+
id: campaign1.id,
30+
name: campaign1.name,
31+
organizationId: organization.id,
32+
organizationName: organization.name,
33+
type: campaign1.type,
34+
targetProfileId: targetProfile.id,
35+
targetProfileName: targetProfile.name,
36+
code: campaign1.code,
37+
createdAt: campaign1.createdAt,
38+
}),
39+
new Campaign({
40+
id: campaign2.id,
41+
name: campaign2.name,
42+
organizationId: organization.id,
43+
organizationName: organization.name,
44+
type: campaign2.type,
45+
targetProfileId: targetProfile.id,
46+
targetProfileName: targetProfile.name,
47+
code: campaign2.code,
48+
createdAt: campaign2.createdAt,
49+
}),
50+
]);
51+
});
52+
});
53+
});

0 commit comments

Comments
 (0)