Skip to content

Commit c21d667

Browse files
[FEATURE] Pouvoir souscrire à un calendrier avec les évènements.
#1
2 parents 4dcd8a6 + 0aab46a commit c21d667

29 files changed

+1112
-89
lines changed

.github/workflows/ci.yml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
ci:
10+
runs-on: ubuntu-latest
11+
12+
services:
13+
postgres:
14+
image: postgres
15+
env:
16+
POSTGRES_PASSWORD: postgres
17+
options: >-
18+
--health-cmd pg_isready
19+
--health-interval 10s
20+
--health-timeout 5s
21+
--health-retries 5
22+
ports:
23+
- 5432:5432
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Setup Node
30+
uses: actions/setup-node@v4
31+
with:
32+
node-version: 20
33+
34+
- name: Install
35+
run: npm ci
36+
37+
- name: Lint
38+
run: npm run lint
39+
40+
- name: Test
41+
run: npm test
42+
env:
43+
NODE_ENV: test
44+
TEST_DATABASE_API_URL: postgres://postgres:postgres@localhost:5432/test

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ Voici les différentes étapes pour pouvoir préparer correctement sa venue :
66

77
![Les différentes étapes de réservation](./docs/étapes-reservation.png)
88

9-
Le projet est composé actuellement de 3 grandes étapes :
9+
Le projet est composé actuellement de 4 grandes étapes :
1010

1111
1. Vérifier qu'une nouvelle réservation a été demandée sur Gymlib
1212
2. Remplir le formulaire de contremarque UCPA
1313
3. Recevoir une notification dès que l'UCPA a validé les informations avec des créneaux arrangeants qui sont disponibles
14+
4. Créer des évènements dans un calendrier et proposer une url pour s'abonner au calendrier `/reservations/calendar`.
1415

1516
A venir :
1617

17-
1. Créer un évènement de calendrier et créer un pass Apple Wallet
18+
1. Créer un pass Apple Wallet pour chaque évènement
1819
2. Me notifier de créneaux qui m'arrangent qui se libèrent
1920

2021
La réservation du créneau se fait donc toujours manuellement sur le site de l'UCPA, mais toutes les étapes contraignantes

config.js

+21-6
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ function isFeatureEnabled(environmentVariable) {
66
return environmentVariable === 'true';
77
}
88

9+
function getParsedJson(environmentVariable) {
10+
if (environmentVariable === undefined) {
11+
return undefined;
12+
}
13+
return JSON.parse(environmentVariable);
14+
}
15+
916
function buildConfiguration() {
1017
const config = {
1118
environment: env.NODE_ENV || 'development',
19+
port: env.PORT || 3000,
1220
logging: {
1321
enabled: isFeatureEnabled(env.LOG_ENABLED),
1422
logLevel: env.LOG_LEVEL || 'info',
@@ -21,7 +29,7 @@ function buildConfiguration() {
2129
user: env.GYMLIB_MAIL_RECEIVER_IMAP_USER,
2230
password: env.GYMLIB_MAIL_RECEIVER_IMAP_PASSWORD,
2331
},
24-
searchQuery: JSON.parse(env.GYMLIB_MAIL_RECEIVER_IMAP_SEARCH_QUERY),
32+
searchQuery: getParsedJson(env.GYMLIB_MAIL_RECEIVER_IMAP_SEARCH_QUERY),
2533
},
2634
ucpa: {
2735
imapConfig: {
@@ -30,16 +38,19 @@ function buildConfiguration() {
3038
user: env.UCPA_MAIL_RECEIVER_IMAP_USER,
3139
password: env.UCPA_MAIL_RECEIVER_IMAP_PASSWORD,
3240
},
33-
searchQuery: JSON.parse(env.UCPA_MAIL_RECEIVER_IMAP_SEARCH_QUERY),
34-
formInfo: JSON.parse(env.FORM_RESPONSE),
41+
searchQuery: getParsedJson(env.UCPA_MAIL_RECEIVER_IMAP_SEARCH_QUERY),
42+
formInfo: getParsedJson(env.FORM_RESPONSE),
3543
formSubmit: isFeatureEnabled(env.FORM_SUBMIT_ENABLED),
3644
areaId: env.UCPA_AREA_ID,
3745
},
3846
notification: {
39-
url: env.NOTFICATION_URL,
40-
token: env.NOTFICATION_TOKEN,
47+
url: env.NOTIFICATION_URL,
48+
token: env.NOTIFICATION_TOKEN,
4149
},
42-
timeSlotsPreferences: JSON.parse(env.TIME_SLOTS_PREFERENCES),
50+
calendar: {
51+
name: env.CALENDAR_NAME,
52+
},
53+
timeSlotsPreferences: getParsedJson(env.TIME_SLOTS_PREFERENCES),
4354
};
4455
if (config.environment === 'test') {
4556
config.logging.enabled = false;
@@ -52,6 +63,10 @@ function buildConfiguration() {
5263
}
5364

5465
function verifyConfig(config) {
66+
if (env.NODE_ENV === 'test') {
67+
return true;
68+
}
69+
5570
let allKeysHaveValues = true;
5671

5772
function checkDeep(object, path) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
const up = async function (knex) {
6+
await knex.schema.alterTable('reservations', (table) => {
7+
table.dateTime('start_at').nullable();
8+
table.string('court').nullable();
9+
table.string('activity').nullable();
10+
});
11+
};
12+
13+
/**
14+
* @param { import("knex").Knex } knex
15+
* @returns { Promise<void> }
16+
*/
17+
const down = async function (knex) {
18+
await knex.schema.alterTable('reservations', (table) => {
19+
table.dropColumn('start_at');
20+
table.dropColumn('court');
21+
table.dropColumn('activity');
22+
});
23+
};
24+
25+
export {
26+
down,
27+
up,
28+
};

db/seeds/.gitkeep

Whitespace-only changes.

index.js

+29-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ import { CronJob } from 'cron';
22

33
import { config } from './config.js';
44

5+
import { createServer } from './server.js';
56
import { ReservationController } from './src/application/ReservationController.js';
7+
import { CreateReservationEventsUseCase } from './src/domain/usecases/CreateReservationEventsUseCase.js';
68
import { GetActiveReservationsUseCase } from './src/domain/usecases/GetActiveReservationsUseCase.js';
9+
import { GetAllEventsUseCase } from './src/domain/usecases/GetAllEventsUseCase.js';
710
import { HandleNewReservationUseCase } from './src/domain/usecases/HandleNewReservationUseCase.js';
11+
import { HandleScheduledReservationUseCase } from './src/domain/usecases/HandleScheduledReservationUseCase.js';
812
import { NotifyUseCase } from './src/domain/usecases/NotifyUseCase.js';
913
import { SubmitFormUseCase } from './src/domain/usecases/SubmitFormUseCase.js';
1014
import { Browser } from './src/infrastructure/Browser.js';
15+
import { CalendarRepository } from './src/infrastructure/CalendarRepository.js';
1116
import { ImapClient } from './src/infrastructure/ImapClient.js';
1217
import { logger } from './src/infrastructure/logger.js';
1318
import { NotificationClient } from './src/infrastructure/NotificationClient.js';
14-
import { reservationRepositories } from './src/infrastructure/ReservationRepositories.js';
19+
import { reservationRepository } from './src/infrastructure/ReservationRepository.js';
1520
import { TimeSlotDatasource } from './src/infrastructure/TimeSlotDatasource.js';
1621

1722
const parisTimezone = 'Europe/Paris';
@@ -30,6 +35,8 @@ async function main() {
3035
start: true,
3136
timeZone: parisTimezone,
3237
});
38+
const server = await createServer({ reservationController });
39+
await server.start();
3340
}
3441

3542
async function getReservationController() {
@@ -40,13 +47,13 @@ async function getReservationController() {
4047
});
4148

4249
const getActiveReservationsUseCase = new GetActiveReservationsUseCase({
43-
reservationRepositories,
50+
reservationRepository,
4451
});
4552

4653
const browser = await Browser.create();
4754
const submitFormUseCase = new SubmitFormUseCase({
4855
browser,
49-
reservationRepositories,
56+
reservationRepository,
5057
formInfo: config.ucpa.formInfo,
5158
dryRun: !config.ucpa.formSubmit,
5259
});
@@ -57,18 +64,36 @@ async function getReservationController() {
5764
const notifyUseCase = new NotifyUseCase({
5865
imapClient: ucpaImapClient,
5966
searchQuery: config.ucpa.searchQuery,
60-
reservationRepositories,
67+
reservationRepository,
6168
timeSlotDatasource,
6269
notificationClient,
6370
timeSlotsPreferences: config.timeSlotsPreferences,
6471
areaId: config.ucpa.areaId,
6572
});
6673

74+
const handleScheduledReservationUseCase = new HandleScheduledReservationUseCase({
75+
imapClient: ucpaImapClient,
76+
searchQuery: config.ucpa.searchQuery,
77+
reservationRepository,
78+
});
79+
80+
const calendarRepository = new CalendarRepository(config.calendar.name);
81+
82+
const createReservationEventsUseCase = new CreateReservationEventsUseCase({
83+
reservationRepository,
84+
calendarRepository,
85+
});
86+
87+
const getAllEventsUseCase = new GetAllEventsUseCase({ calendarRepository });
88+
6789
return new ReservationController({
6890
handleNewReservationUseCase,
6991
getActiveReservationsUseCase,
7092
submitFormUseCase,
7193
notifyUseCase,
94+
handleScheduledReservationUseCase,
95+
createReservationEventsUseCase,
96+
getAllEventsUseCase,
7297
logger,
7398
});
7499
}

0 commit comments

Comments
 (0)