Skip to content

Commit 229c18a

Browse files
[FEATURE] Proposer le QRCode des évènements via un pass Apple Wallet
Merge pull request #2 from VincentHardouin/feat-add-pass
2 parents 5e446fe + 1b9e29c commit 229c18a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1383
-91
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
uses: actions/setup-node@v4
3131
with:
3232
node-version: 20
33-
cache: 'npm'
33+
cache: npm
3434

3535
- name: Install
3636
run: npm ci

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
2-
.env
2+
.env
3+
certs/

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,16 +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 4 grandes étapes :
9+
Le projet est composé actuellement de 5 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
1414
4. Créer des évènements dans un calendrier et proposer une url pour s'abonner au calendrier `/reservations/calendar`.
15+
5. Créer un pass Apple Wallet qui se met à jour pour chaque réservation
1516

1617
A venir :
1718

18-
1. Créer un pass Apple Wallet pour chaque évènement
1919
2. Me notifier de créneaux qui m'arrangent qui se libèrent
2020

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

config.js

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ function buildConfiguration() {
1717
const config = {
1818
environment: env.NODE_ENV || 'development',
1919
port: env.PORT || 3000,
20+
baseURL: env.BASE_URL || 'http://example.net',
21+
secret: env.SECRET,
2022
logging: {
2123
enabled: isFeatureEnabled(env.LOG_ENABLED),
2224
logLevel: env.LOG_LEVEL || 'info',
@@ -52,9 +54,18 @@ function buildConfiguration() {
5254
id: env.CALENDAR_ID,
5355
},
5456
timeSlotsPreferences: getParsedJson(env.TIME_SLOTS_PREFERENCES),
57+
certificates: {
58+
signerKeyPassphrase: env.CERTIFICATES_SIGNER_KEY_PASSPHRASE,
59+
},
60+
pass: {
61+
passTypeIdentifier: env.PASS_TYPE_IDENTIFIER,
62+
teamIdentifier: env.PASS_TEAM_IDENTIFIER,
63+
},
5564
};
5665
if (config.environment === 'test') {
5766
config.logging.enabled = false;
67+
config.secret = 'SECRET_FOR_TESTS';
68+
config.pass.passTypeIdentifier = 'pass-identifier';
5869
}
5970

6071
if (!verifyConfig(config)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @param { import("knex").Knex } knex
3+
* @returns { Promise<void> }
4+
*/
5+
const up = async function (knex) {
6+
await knex.schema.createTable('devices', (table) => {
7+
table.string('deviceLibraryIdentifier').primary();
8+
table.string('pushToken').notNullable();
9+
table.dateTime('created_at').notNullable().defaultTo(knex.fn.now());
10+
});
11+
12+
await knex.schema.createTable('passes', (table) => {
13+
table.string('passTypeIdentifier').notNullable();
14+
table.string('serialNumber').notNullable().unique();
15+
table.string('nextEvent').defaultTo(null);
16+
table.timestamp('updated_at').notNullable().defaultTo(knex.fn.now());
17+
});
18+
19+
await knex.schema.createTable('registrations', (table) => {
20+
table.string('passTypeIdentifier');
21+
table.string('serialNumber').references('passes.serialNumber');
22+
table.string('deviceLibraryIdentifier').references('devices.deviceLibraryIdentifier');
23+
table.dateTime('created_at').notNullable().defaultTo(knex.fn.now());
24+
});
25+
};
26+
27+
/**
28+
* @param { import("knex").Knex } knex
29+
* @returns { Promise<void> }
30+
*/
31+
const down = async function (knex) {
32+
await knex.schema.dropTable('registrations');
33+
await knex.schema.dropTable('passes');
34+
await knex.schema.dropTable('devices');
35+
};
36+
37+
export {
38+
down,
39+
up,
40+
};

docs/favicon.ico

4.19 KB
Binary file not shown.

index.js

+5-77
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,16 @@
11
import { CronJob } from 'cron';
22

33
import { config } from './config.js';
4-
54
import { createServer } from './server.js';
6-
import { ReservationController } from './src/application/ReservationController.js';
7-
import { CreateReservationEventsUseCase } from './src/domain/usecases/CreateReservationEventsUseCase.js';
8-
import { GetActiveReservationsUseCase } from './src/domain/usecases/GetActiveReservationsUseCase.js';
9-
import { GetAllEventsUseCase } from './src/domain/usecases/GetAllEventsUseCase.js';
10-
import { HandleNewReservationUseCase } from './src/domain/usecases/HandleNewReservationUseCase.js';
11-
import { HandleScheduledReservationUseCase } from './src/domain/usecases/HandleScheduledReservationUseCase.js';
12-
import { NotifyUseCase } from './src/domain/usecases/NotifyUseCase.js';
13-
import { SubmitFormUseCase } from './src/domain/usecases/SubmitFormUseCase.js';
14-
import { Browser } from './src/infrastructure/Browser.js';
15-
import { CalendarRepository } from './src/infrastructure/CalendarRepository.js';
16-
import { ImapClient } from './src/infrastructure/ImapClient.js';
5+
import { passController, reservationController } from './src/application/index.js';
6+
import { authService } from './src/infrastructure/AuthService.js';
177
import { logger } from './src/infrastructure/logger.js';
18-
import { NotificationClient } from './src/infrastructure/NotificationClient.js';
19-
import { reservationRepository } from './src/infrastructure/ReservationRepository.js';
20-
import { TimeSlotDatasource } from './src/infrastructure/TimeSlotDatasource.js';
218

229
const parisTimezone = 'Europe/Paris';
2310

2411
main();
2512

2613
async function main() {
27-
const reservationController = await getReservationController();
2814
CronJob.from({
2915
cronTime: config.cronTime,
3016
onTick: async () => {
@@ -33,67 +19,9 @@ async function main() {
3319
logger.info('End job');
3420
},
3521
start: true,
22+
runOnInit: true,
3623
timeZone: parisTimezone,
3724
});
38-
const server = await createServer({ reservationController });
25+
const server = await createServer({ reservationController, authService, passController });
3926
await server.start();
40-
}
41-
42-
async function getReservationController() {
43-
const gymlibImapClient = new ImapClient(config.gymlib.imapConfig);
44-
const handleNewReservationUseCase = new HandleNewReservationUseCase({
45-
imapClient: gymlibImapClient,
46-
searchQuery: config.gymlib.searchQuery,
47-
});
48-
49-
const getActiveReservationsUseCase = new GetActiveReservationsUseCase({
50-
reservationRepository,
51-
});
52-
53-
const browser = await Browser.create();
54-
const submitFormUseCase = new SubmitFormUseCase({
55-
browser,
56-
reservationRepository,
57-
formInfo: config.ucpa.formInfo,
58-
dryRun: !config.ucpa.formSubmit,
59-
});
60-
61-
const ucpaImapClient = new ImapClient(config.ucpa.imapConfig);
62-
const timeSlotDatasource = new TimeSlotDatasource();
63-
const notificationClient = new NotificationClient(config.notification);
64-
const notifyUseCase = new NotifyUseCase({
65-
imapClient: ucpaImapClient,
66-
searchQuery: config.ucpa.searchQuery,
67-
reservationRepository,
68-
timeSlotDatasource,
69-
notificationClient,
70-
timeSlotsPreferences: config.timeSlotsPreferences,
71-
areaId: config.ucpa.areaId,
72-
});
73-
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-
89-
return new ReservationController({
90-
handleNewReservationUseCase,
91-
getActiveReservationsUseCase,
92-
submitFormUseCase,
93-
notifyUseCase,
94-
handleScheduledReservationUseCase,
95-
createReservationEventsUseCase,
96-
getAllEventsUseCase,
97-
logger,
98-
});
99-
}
27+
};

model.pass/background.png

8.88 KB
Loading

model.pass/background@2x.png

8.88 KB
Loading

model.pass/icon.png

4.47 KB
Loading

model.pass/icon@2x.png

8.8 KB
Loading

model.pass/logo.png

4.1 KB
Loading

model.pass/logo@2x.png

4.1 KB
Loading

model.pass/pass.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"formatVersion": 1,
3+
"locations": [
4+
{
5+
"longitude": 2.371645,
6+
"latitude": 48.896370
7+
}
8+
],
9+
"organizationName": "Vincent Hardouin",
10+
"description": "UCPA Reservation ticket",
11+
"labelColor": "rgb(255, 255, 255)",
12+
"foregroundColor": "rgb(255, 255, 255)",
13+
"backgroundColor": "rgb(76, 19, 223)",
14+
"eventTicket": {
15+
"primaryFields": [
16+
],
17+
"secondaryFields": [
18+
]
19+
}
20+
}

0 commit comments

Comments
 (0)