Skip to content

Commit 795a8ee

Browse files
[FEATURE] Ajout d'une route de mise à disposition pour les résultats de campagne
#11868
2 parents e68d550 + 83ea761 commit 795a8ee

18 files changed

+403
-16
lines changed

Diff for: api/db/seeds/data/common/common-builder.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function createClientApplications(databaseBuilder) {
9797
name: 'multi-organizations-client-application',
9898
clientId: 'maddo-client',
9999
clientSecret: 'maddo-secret',
100-
scopes: ['meta'],
100+
scopes: ['meta', 'campaigns'],
101101
jurisdiction: { rules: [{ name: 'tags', value: [COLLEGE_TAG.name] }] },
102102
});
103103
}

Diff for: api/server.maddo.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { setupErrorHandling } from './config/server-setup-error-handling.js';
66
import { knex } from './db/knex-database-connection.js';
77
import { authentication } from './lib/infrastructure/authentication.js';
88
import { identityAccessManagementRoutes } from './src/identity-access-management/application/routes.js';
9-
import * as organizationRoutes from './src/maddo/application/organizations-routes.js';
10-
import * as replicationRoutes from './src/maddo/application/replications-routes.js';
9+
import * as campaignsRoutes from './src/maddo/application/campaigns-routes.js';
10+
import * as organizationsRoutes from './src/maddo/application/organizations-routes.js';
11+
import * as replicationsRoutes from './src/maddo/application/replications-routes.js';
1112
import { Metrics } from './src/monitoring/infrastructure/metrics.js';
1213
import * as healthcheckRoutes from './src/shared/application/healthcheck/index.js';
1314
import { config } from './src/shared/config.js';
@@ -180,7 +181,13 @@ const setupAuthentication = function (server) {
180181
};
181182

182183
const setupRoutesAndPlugins = async function (server) {
183-
const routes = [healthcheckRoutes, ...identityAccessManagementRoutes, replicationRoutes, organizationRoutes];
184+
const routes = [
185+
...identityAccessManagementRoutes,
186+
campaignsRoutes,
187+
healthcheckRoutes,
188+
organizationsRoutes,
189+
replicationsRoutes,
190+
];
184191
const routesWithOptions = routes.map((route) => ({
185192
plugin: route,
186193
options: { tags: ['maddo'] },

Diff for: api/src/maddo/application/campaigns-controller.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { usecases } from '../domain/usecases/index.js';
2+
3+
export async function getCampaignParticipations(
4+
request,
5+
h,
6+
dependencies = { getCampaignParticipations: usecases.getCampaignParticipations },
7+
) {
8+
const campaignParticipations = await dependencies.getCampaignParticipations({
9+
campaignId: request.params.campaignId,
10+
});
11+
return h.response(campaignParticipations).code(200);
12+
}

Diff for: api/src/maddo/application/campaigns-routes.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Joi from 'joi';
2+
3+
import { identifiersType } from '../../shared/domain/types/identifiers-type.js';
4+
import { getCampaignParticipations } from './campaigns-controller.js';
5+
import { isCampaignInJurisdictionPreHandler, organizationPreHandler } from './pre-handlers.js';
6+
7+
const register = async function (server) {
8+
server.route([
9+
{
10+
method: 'GET',
11+
path: '/api/campaigns/{campaignId}/participations',
12+
config: {
13+
auth: { access: { scope: 'campaigns' } },
14+
validate: {
15+
params: Joi.object({
16+
campaignId: identifiersType.campaignId,
17+
}),
18+
},
19+
pre: [organizationPreHandler, isCampaignInJurisdictionPreHandler],
20+
handler: getCampaignParticipations,
21+
notes: ['- Retourne les résultats de la campagne donnée.'],
22+
tags: ['api', 'campaigns', 'maddo'],
23+
},
24+
},
25+
]);
26+
};
27+
28+
const name = 'maddo-campaigns-api';
29+
export { name, register };
-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import boom from '@hapi/boom';
2-
31
import { usecases } from '../domain/usecases/index.js';
42

53
export async function getOrganizations(request, h, dependencies = { findOrganizations: usecases.findOrganizations }) {
@@ -8,11 +6,7 @@ export async function getOrganizations(request, h, dependencies = { findOrganiza
86
}
97

108
export async function getOrganizationCampaigns(request, h, dependencies = { findCampaigns: usecases.findCampaigns }) {
11-
const organizationIds = request.pre.organizationIds;
129
const requestedOrganizationId = request.params.organizationId;
13-
if (!organizationIds.includes(requestedOrganizationId)) {
14-
return boom.forbidden();
15-
}
1610
const campaigns = await dependencies.findCampaigns({ organizationId: requestedOrganizationId });
1711
return h.response(campaigns).code(200);
1812
}

Diff for: api/src/maddo/application/organizations-routes.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Joi from 'joi';
22

33
import { identifiersType } from '../../shared/domain/types/identifiers-type.js';
44
import { getOrganizationCampaigns, getOrganizations } from './organizations-controller.js';
5-
import { organizationPreHandler } from './pre-handlers.js';
5+
import { isOrganizationInJurisdictionPreHandler, organizationPreHandler } from './pre-handlers.js';
66

77
const register = async function (server) {
88
server.route([
@@ -21,15 +21,15 @@ const register = async function (server) {
2121
method: 'GET',
2222
path: '/api/organizations/{organizationId}/campaigns',
2323
config: {
24-
auth: { access: { scope: 'meta' } },
24+
auth: { access: { scope: 'campaigns' } },
2525
validate: {
2626
params: Joi.object({
2727
organizationId: identifiersType.organizationId,
2828
}),
2929
},
30-
pre: [organizationPreHandler],
30+
pre: [organizationPreHandler, isOrganizationInJurisdictionPreHandler],
3131
handler: getOrganizationCampaigns,
32-
notes: ["- Retourne la liste des campaignes de l'organisation fournie"],
32+
notes: ["- Retourne la liste des campaignes de l'organisation donnée"],
3333
tags: ['api', 'meta'],
3434
},
3535
},

Diff for: api/src/maddo/application/pre-handlers.js

+27
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import boom from '@hapi/boom';
2+
13
import { usecases } from '../domain/usecases/index.js';
24

35
export const organizationPreHandler = {
@@ -10,3 +12,28 @@ export const organizationPreHandler = {
1012
return dependencies.findOrganizationIdsByClientApplication({ clientId: request.auth.credentials.client_id });
1113
},
1214
};
15+
16+
export const isOrganizationInJurisdictionPreHandler = {
17+
method: function (request, h) {
18+
return isOrganizationInJurisdiction(request.pre?.organizationIds, request.params.organizationId, h);
19+
},
20+
};
21+
22+
export const isCampaignInJurisdictionPreHandler = {
23+
method: async function (
24+
request,
25+
h,
26+
dependencies = { getCampaignOrganizationId: usecases.getCampaignOrganizationId },
27+
) {
28+
const { campaignId } = request.params;
29+
const organizationId = await dependencies.getCampaignOrganizationId({ campaignId });
30+
return isOrganizationInJurisdiction(request.pre?.organizationIds, organizationId, h);
31+
},
32+
};
33+
34+
function isOrganizationInJurisdiction(jurisdictionOrganizationIds = [], organizationId, h) {
35+
if (!jurisdictionOrganizationIds.includes(organizationId)) {
36+
return boom.forbidden();
37+
}
38+
return h.continue;
39+
}

Diff for: api/src/maddo/domain/models/CampaignParticipation.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class CampaignParticipation {
2+
constructor({
3+
id,
4+
createdAt,
5+
participantExternalId,
6+
status,
7+
sharedAt,
8+
campaignId,
9+
userId,
10+
organizationLearnerId,
11+
} = {}) {
12+
this.id = id;
13+
this.createdAt = createdAt;
14+
this.status = status;
15+
this.participantExternalId = participantExternalId;
16+
this.sharedAt = sharedAt;
17+
this.campaignId = campaignId;
18+
this.userId = userId;
19+
this.status = status;
20+
this.organizationLearnerId = organizationLearnerId;
21+
}
22+
}
23+
24+
export { CampaignParticipation };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export async function getCampaignOrganizationId({ campaignId, campaignRepository }) {
2+
return campaignRepository.getOrganizationId(campaignId);
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export async function getCampaignParticipations({ campaignId, campaignParticipationRepository }) {
2+
return campaignParticipationRepository.findByCampaignId(campaignId);
3+
}

Diff for: 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 campaignParticipationRepository from '../../infrastructure/repositories/campaign-participation-repository.js';
67
import * as campaignRepository from '../../infrastructure/repositories/campaign-repository.js';
78
import * as clientApplicationRepository from '../../infrastructure/repositories/client-application-repository.js';
89
import * as organizationRepository from '../../infrastructure/repositories/organization-repository.js';
@@ -13,6 +14,7 @@ const dependencies = {
1314
clientApplicationRepository,
1415
organizationRepository,
1516
campaignRepository,
17+
campaignParticipationRepository,
1618
};
1719

1820
const usecasesWithoutInjectedDependencies = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { knex } from '../../../../db/knex-database-connection.js';
2+
import { CampaignParticipation } from '../../domain/models/CampaignParticipation.js';
3+
4+
export async function findByCampaignId(campaignId) {
5+
const rawCampaigns = await knex
6+
.select(
7+
'id',
8+
'createdAt',
9+
'participantExternalId',
10+
'status',
11+
'sharedAt',
12+
'deletedAt',
13+
'deletedBy',
14+
'campaignId',
15+
'userId',
16+
'organizationLearnerId',
17+
)
18+
.from('campaign-participations')
19+
.where('campaignId', campaignId)
20+
.orderBy('id');
21+
return rawCampaigns.map(toDomain);
22+
}
23+
24+
function toDomain(rawCampaignParticipation) {
25+
return new CampaignParticipation(rawCampaignParticipation);
26+
}

Diff for: api/src/maddo/infrastructure/repositories/campaign-repository.js

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export async function findByOrganizationId(organizationId) {
2222
return rawCampaigns.map(toDomain);
2323
}
2424

25+
export async function getOrganizationId(campaignId) {
26+
const [organizationId] = await knex.pluck('organizationId').from('campaigns').where('id', campaignId);
27+
return organizationId;
28+
}
29+
2530
function toDomain(rawCampaign) {
2631
return new Campaign(rawCampaign);
2732
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { CampaignParticipation } from '../../../../src/maddo/domain/models/CampaignParticipation.js';
2+
import { CampaignParticipationStatuses } from '../../../../src/prescription/shared/domain/constants.js';
3+
import {
4+
createMaddoServer,
5+
databaseBuilder,
6+
expect,
7+
generateValidRequestAuthorizationHeaderForApplication,
8+
} from '../../../test-helper.js';
9+
10+
describe('Acceptance | Maddo | Route | Campaigns', function () {
11+
let server;
12+
13+
beforeEach(async function () {
14+
server = await createMaddoServer();
15+
});
16+
17+
describe('GET /api/campaigns/{campaignId}/participations', function () {
18+
it('returns the list of all participations of campaign with an HTTP status code 200', async function () {
19+
// given
20+
const orgaInJurisdiction = databaseBuilder.factory.buildOrganization({ name: 'orga-in-jurisdiction' });
21+
databaseBuilder.factory.buildOrganization({ name: 'orga-not-in-jurisdiction' });
22+
23+
const tag = databaseBuilder.factory.buildTag();
24+
databaseBuilder.factory.buildOrganizationTag({ organizationId: orgaInJurisdiction.id, tagId: tag.id });
25+
26+
const clientId = 'client';
27+
databaseBuilder.factory.buildClientApplication({
28+
clientId: 'client',
29+
jurisdiction: { rules: [{ name: 'tags', value: [tag.name] }] },
30+
});
31+
32+
const campaign = databaseBuilder.factory.buildCampaign({ organizationId: orgaInJurisdiction.id });
33+
const participation1 = databaseBuilder.factory.buildCampaignParticipation({ campaignId: campaign.id });
34+
const participation2 = databaseBuilder.factory.buildCampaignParticipation({
35+
campaignId: campaign.id,
36+
status: CampaignParticipationStatuses.STARTED,
37+
});
38+
39+
await databaseBuilder.commit();
40+
41+
const options = {
42+
method: 'GET',
43+
url: `/api/campaigns/${campaign.id}/participations`,
44+
headers: {
45+
authorization: generateValidRequestAuthorizationHeaderForApplication(clientId, 'pix-client', 'campaigns'),
46+
},
47+
};
48+
49+
// when
50+
const response = await server.inject(options);
51+
52+
// then
53+
expect(response.statusCode).to.equal(200);
54+
expect(response.result).to.deep.equal([
55+
new CampaignParticipation(participation1),
56+
new CampaignParticipation(participation2),
57+
]);
58+
});
59+
});
60+
});

Diff for: api/tests/maddo/application/acceptance/organizations-routes_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('Acceptance | Maddo | Route | Organizations', function () {
9292
method: 'GET',
9393
url: `/api/organizations/${orgaInJurisdiction.id}/campaigns`,
9494
headers: {
95-
authorization: generateValidRequestAuthorizationHeaderForApplication(clientId, 'pix-client', 'meta'),
95+
authorization: generateValidRequestAuthorizationHeaderForApplication(clientId, 'pix-client', 'campaigns'),
9696
},
9797
};
9898

0 commit comments

Comments
 (0)