From deb21fbf42660e4d76f78b9365d77f3c41979733 Mon Sep 17 00:00:00 2001 From: Antoine Pelletier Date: Thu, 23 Oct 2025 15:49:20 +0200 Subject: [PATCH] feat: add conditions --- backend/server.js | 77 +++++++++++ index.html | 2 +- src/main.js | 16 +++ src/pages/AdminPage.vue | 272 +++++++++++++++++++++++++------------- src/pages/BookingPage.vue | 55 ++++++-- 5 files changed, 315 insertions(+), 107 deletions(-) diff --git a/backend/server.js b/backend/server.js index 8880cb1..523546e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -10,6 +10,22 @@ const app = express() app.use(cors()) app.use(express.json()) +// Helper: combine ISO date (YYYY-MM-DD) and time (HH:mm) into local Date +function parseLocalDateTime(dateStr, timeStr, end = false) { + if (!dateStr) return null + const [y, m, d] = String(dateStr).split('-').map(Number) + let hh = 0, mm = 0 + if (typeof timeStr === 'string' && timeStr.includes(':')) { + const [h, mi] = timeStr.split(':').map(Number) + hh = Number.isFinite(h) ? h : 0 + mm = Number.isFinite(mi) ? mi : 0 + } else if (end) { + hh = 23; mm = 59 + } + if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return null + return new Date(y, m - 1, d, hh, mm, 0, 0) +} + // Helper: normalize booking row fields to stable strings (avoid timezone surprises) function sanitizeBooking(row) { const normDate = (v) => { @@ -87,6 +103,30 @@ app.post('/api/bookings', async (req, res) => { return res.status(400).json({ error: 'Missing fields' }) } + // Validate temporal constraints: require start_time/end_time and ensure start >= now, end > start + if (!start_time || !end_time) { + return res.status(400).json({ error: 'Start and end times are required' }) + } + const s = parseLocalDateTime(start_date, start_time) + const e = parseLocalDateTime(end_date, end_time, true) + if (!s || !e) { + return res.status(400).json({ error: 'Invalid dates' }) + } + if (e <= s) { + return res.status(400).json({ error: 'End must be after start' }) + } + const now = new Date() + if (s < now) { + return res.status(400).json({ error: 'Start cannot be in the past' }) + } + // Enforce max 31 days in advance for start and end + const max = new Date() + max.setDate(max.getDate() + 31) + max.setHours(23, 59, 59, 999) + if (s > max || e > max) { + return res.status(400).json({ error: 'Bookings cannot be made more than 31 days in advance' }) + } + // Explicitly set status to 'pending' to avoid relying solely on DB defaults const text = `INSERT INTO bookings(bike_type, bike_types, start_date, start_time, end_date, end_time, name, email, status) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *` @@ -244,6 +284,43 @@ app.patch('/api/bookings/:id', async (req, res) => { if (entries.length === 0) return res.status(400).json({ error: 'No valid fields to update' }) + // Get current booking to validate temporal changes + const current = await pool.query('SELECT * FROM bookings WHERE id = $1', [id]) + if (current.rowCount === 0) return res.status(404).json({ error: 'Booking not found' }) + const existing = current.rows[0] + + // Validate temporal constraints if dates/times are being updated + const bodyObj = req.body || {} + const startDate = bodyObj.start_date || existing.start_date + const startTime = bodyObj.start_time || existing.start_time + const endDate = bodyObj.end_date || existing.end_date + const endTime = bodyObj.end_time || existing.end_time + + if (startDate && endDate && startTime && endTime) { + const s = parseLocalDateTime(startDate, startTime) + const e = parseLocalDateTime(endDate, endTime, true) + if (!s || !e) { + return res.status(400).json({ error: 'Invalid dates' }) + } + if (e <= s) { + return res.status(400).json({ error: 'End must be after start' }) + } + // Allow editing up to 1 day in the past + const oneDayAgo = new Date() + oneDayAgo.setDate(oneDayAgo.getDate() - 1) + oneDayAgo.setHours(0, 0, 0, 0) + if (s < oneDayAgo) { + return res.status(400).json({ error: 'Start cannot be more than 1 day in the past' }) + } + // Enforce max 31 days in advance for start and end + const max = new Date() + max.setDate(max.getDate() + 31) + max.setHours(23, 59, 59, 999) + if (s > max || e > max) { + return res.status(400).json({ error: 'Bookings cannot be made more than 31 days in advance' }) + } + } + // Build dynamic update const sets = [] const values = [] diff --git a/index.html b/index.html index b330a06..80a7bc5 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite App + Cargobikes
diff --git a/src/main.js b/src/main.js index 036f2bc..a524592 100644 --- a/src/main.js +++ b/src/main.js @@ -4,6 +4,22 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router' +// Set Agepoly logo as favicon at runtime +import agepolyLogoUrl from '@/assets/cargobike-icon.png' +function setFavicon(href) { + try { + const doc = document + let link = doc.querySelector('link[rel="icon"]') || doc.createElement('link') + link.rel = 'icon' + link.type = 'image/png' + link.href = href + if (!link.parentNode) doc.head.appendChild(link) + } catch {} +} + const app = createApp(App) app.use(router) app.mount('#app') + +// After mount, ensure favicon is set +setFavicon(agepolyLogoUrl) diff --git a/src/pages/AdminPage.vue b/src/pages/AdminPage.vue index 17c157c..cfc18a8 100644 --- a/src/pages/AdminPage.vue +++ b/src/pages/AdminPage.vue @@ -10,7 +10,9 @@
Dernier chargement: {{ formatDateTime(lastLoaded) }} - {{ error }} +
+ {{ error }} +
@@ -68,8 +70,8 @@
- - + +
Créée le {{ formatDateTime(new Date(b.created_at)) }}
@@ -107,12 +109,12 @@
- +
@@ -155,9 +157,9 @@
@@ -242,37 +244,64 @@ - +
-
-
-

Attribuer des cargobikes

- +
+ +
+

Modifier la réservation #{{ editingId }}

+
-
-
-
Grands cargos
-
- + + +
+
+ +
+
+
Grands cargos
+
+ +
+
+
+
Petits cargos
+
+ +
+
+
+ + +
+
Période de réservation
+ + +
+ + +
+ + +
+ + +
-
-
Petits cargos
-
- -
-
-
- - -
+
+ + +
+ +
@@ -280,8 +309,64 @@ diff --git a/src/pages/BookingPage.vue b/src/pages/BookingPage.vue index f06f970..ef040dd 100644 --- a/src/pages/BookingPage.vue +++ b/src/pages/BookingPage.vue @@ -11,8 +11,10 @@
- -

{{ errors.association }}

+ +
+

{{ errors.association }}

+
@@ -45,34 +47,42 @@
-
Sélection : {{ form.bikeTypes.join(', ') }}
-

{{ errors.bikeTypes }}

+
Sélection : {{ form.bikeTypes.join(', ') }}
+
+

{{ errors.bikeTypes }}

+
Début de la réservation - +
- + + +
+
+

{{ errors.start }}

-

{{ errors.start }}

Fin de la réservation
- + + +
+
+

{{ errors.end }}

-

{{ errors.end }}

Adresses mail du/des comptes Linka Go à autoriser
-
+
@@ -80,7 +90,9 @@
-

{{ errors.emails }}

+
+

{{ errors.emails }}

+
@@ -118,7 +130,7 @@