From 5258ed3f2a38c8f8879853f487d18b204d5d0b71 Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Sun, 24 Nov 2024 15:30:50 +0100
Subject: [PATCH 1/6] Added server limit to event title and description
---
supabase/migrations/202401_init.sql | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/supabase/migrations/202401_init.sql b/supabase/migrations/202401_init.sql
index 5d34e3a..efb4546 100644
--- a/supabase/migrations/202401_init.sql
+++ b/supabase/migrations/202401_init.sql
@@ -88,6 +88,14 @@ create table public.calendar_events (
);
comment on table public.calendar_events is 'Events linked to a world';
+-- Calendar Events restrictions
+alter table public.calendar_events
+ add constraint calendar_events_maxlen_check
+ check (
+ char_length(title) <= 240 AND
+ (description IS NULL OR char_length(description) <= 1200)
+ );
+
-- Link table for events - categories
create table public.calendar_event_categories_links (
calendar_event_id bigint references public.calendar_events on delete cascade,
From c7069164b01d70ddbd338669568b534bfdebf1b9 Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Sun, 24 Nov 2024 19:56:00 +0100
Subject: [PATCH 2/6] Added check on client side (api)
---
components/calendar/form/UpdateEvent.vue | 22 +++++++++++++++++-----
components/ui/toast/ToastViewport.vue | 2 +-
i18n.config.ts | 10 ++++++++++
models/CalendarEvent.ts | 4 ++--
models/Errors.ts | 14 ++++++++++++++
nuxt.config.ts | 2 +-
server/api/calendars/events/[id].patch.ts | 16 +++++++++++++---
server/api/calendars/events/create.post.ts | 2 ++
stores/CalendarStore.ts | 4 ++--
stores/EventStore.ts | 16 ++++++----------
10 files changed, 68 insertions(+), 24 deletions(-)
create mode 100644 models/Errors.ts
diff --git a/components/calendar/form/UpdateEvent.vue b/components/calendar/form/UpdateEvent.vue
index a814efd..70a5652 100644
--- a/components/calendar/form/UpdateEvent.vue
+++ b/components/calendar/form/UpdateEvent.vue
@@ -1,9 +1,14 @@
-
+
diff --git a/i18n.config.ts b/i18n.config.ts
index d59fc6e..7653fcd 100644
--- a/i18n.config.ts
+++ b/i18n.config.ts
@@ -140,6 +140,11 @@ export default defineI18nConfig(() => ({
title: "Edit event",
subtitle: "Update event data",
},
+ editErrors: {
+ toastTitle: "Event wasn't updated.",
+ title_too_big: "Title should be less than 240 characters long.",
+ description_too_big: "Description should be less than 1200 characters long.",
+ },
deleteDialog: {
title: "Delete this event",
subtitle: "Data associated with this event will be lost and you won't be able to retrieve it !",
@@ -348,6 +353,11 @@ export default defineI18nConfig(() => ({
title: "Modifier l'évènement",
subtitle: "Mettre à jour les données de l'évènement",
},
+ editErrors: {
+ toastTitle: "L'évènement n'a pas été modifié",
+ title_too_big: "Le titre doit être inférieur à 240 caractères.",
+ description_too_big: "La description doit être inférieure à 1200 caractères.",
+ },
deleteDialog: {
title: "Supprimer l'évènement",
subtitle: "Les données associés à cet évènement seront supprimées et vous ne pourrez plus les récupérer !",
diff --git a/models/CalendarEvent.ts b/models/CalendarEvent.ts
index 042b43d..43cfc9e 100644
--- a/models/CalendarEvent.ts
+++ b/models/CalendarEvent.ts
@@ -20,8 +20,8 @@ export interface CalendarEvent {
*/
export const postEventBodySchema = z.object({
event: z.object({
- title: z.string(),
- description: z.string().optional().nullable(),
+ title: z.string().max(240),
+ description: z.string().max(1200).optional().nullable(),
location: z.string().optional().nullable(),
startDate: dateSchema.required(),
endDate: dateSchema.optional().nullable(),
diff --git a/models/Errors.ts b/models/Errors.ts
new file mode 100644
index 0000000..16b6e8d
--- /dev/null
+++ b/models/Errors.ts
@@ -0,0 +1,14 @@
+export interface ValidationError {
+ path: (string | number)[]
+ message: string
+ code: string
+}
+
+export interface APIError {
+ statusCode: number
+ statusMessage: string
+ message: string
+ data: {
+ errors: ValidationError[]
+ }
+}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 0dadd11..f4eea9f 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,6 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
- devtools: { enabled: true },
+ devtools: { enabled: false },
modules: [
"@nuxtjs/supabase",
diff --git a/server/api/calendars/events/[id].patch.ts b/server/api/calendars/events/[id].patch.ts
index f0ab3d9..46ed8c2 100644
--- a/server/api/calendars/events/[id].patch.ts
+++ b/server/api/calendars/events/[id].patch.ts
@@ -22,12 +22,22 @@ export default defineEventHandler(async (event) => {
}
if (bodyError) {
- throw createError({
+ const error = createError({
cause: "Utilisateur",
fatal: false,
- message: "Le schéma de la requête n'est pas complet ou mal renseigné.",
- status: 401,
+ statusCode: 401,
+ statusMessage: "Validation Error",
+ message: "Erreur de validation du schéma, cela est probablement dû à une erreur utilisateur.",
+ data: {
+ errors: bodyError.issues.map(issue => ({
+ path: issue.path,
+ message: issue.message,
+ code: issue.code
+ }))
+ }
})
+
+ throw error
}
try {
diff --git a/server/api/calendars/events/create.post.ts b/server/api/calendars/events/create.post.ts
index 780aa46..46efe29 100644
--- a/server/api/calendars/events/create.post.ts
+++ b/server/api/calendars/events/create.post.ts
@@ -11,6 +11,8 @@ export default defineEventHandler(async (event) => {
cause: "Utilisateur",
fatal: false,
message: "Le schéma de la requête n'est pas complet ou mal renseigné.",
+ data: schemaError.issues,
+ statusMessage: "test",
status: 401,
})
}
diff --git a/stores/CalendarStore.ts b/stores/CalendarStore.ts
index 35acc89..cdbe5f2 100644
--- a/stores/CalendarStore.ts
+++ b/stores/CalendarStore.ts
@@ -869,8 +869,8 @@ export const useCalendar = defineStore("calendar", () => {
const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
baseEvents.value[eventIndex] = res
- } catch (err) {
- console.log(err)
+
+ return res
} finally {
abortController = null
isUpdatingEvent.value = false
diff --git a/stores/EventStore.ts b/stores/EventStore.ts
index 3eecbac..935c59c 100644
--- a/stores/EventStore.ts
+++ b/stores/EventStore.ts
@@ -247,17 +247,13 @@ export const useCalendarEvents = defineStore("calendar-events", () => {
abortController = new AbortController()
isUpdatingEvent.value = true
- try {
- const res = await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: "PATCH", body: { event : eventSkeleton.value, calendarId: activeCalendar?.id }, signal: abortController.signal })
+ const res = await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: "PATCH", body: { event : eventSkeleton.value, calendarId: activeCalendar?.id }, signal: abortController.signal })
- const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
- baseEvents.value[eventIndex] = res
- } catch (err) {
- console.log(err)
- } finally {
- abortController = null
- isUpdatingEvent.value = false
- }
+ const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
+ baseEvents.value[eventIndex] = res
+
+ abortController = null
+ isUpdatingEvent.value = false
}
async function deleteEventFromSkeleton() {
From 372c7a49e1ee5d2b8d22dd6e638d75d8b2dc9f66 Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Sun, 24 Nov 2024 20:08:30 +0100
Subject: [PATCH 3/6] Added validation for location as well
---
i18n.config.ts | 2 ++
models/CalendarEvent.ts | 2 +-
server/api/calendars/events/[id].patch.ts | 2 +-
server/api/calendars/events/create.post.ts | 18 +++++++++++++-----
supabase/migrations/202401_init.sql | 3 ++-
5 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/i18n.config.ts b/i18n.config.ts
index 7653fcd..e655bfc 100644
--- a/i18n.config.ts
+++ b/i18n.config.ts
@@ -144,6 +144,7 @@ export default defineI18nConfig(() => ({
toastTitle: "Event wasn't updated.",
title_too_big: "Title should be less than 240 characters long.",
description_too_big: "Description should be less than 1200 characters long.",
+ location_too_big: "Location should be less than 240 characters long.",
},
deleteDialog: {
title: "Delete this event",
@@ -357,6 +358,7 @@ export default defineI18nConfig(() => ({
toastTitle: "L'évènement n'a pas été modifié",
title_too_big: "Le titre doit être inférieur à 240 caractères.",
description_too_big: "La description doit être inférieure à 1200 caractères.",
+ location_too_big: "La localisation doit être inférieure à 240 caractères.",
},
deleteDialog: {
title: "Supprimer l'évènement",
diff --git a/models/CalendarEvent.ts b/models/CalendarEvent.ts
index 43cfc9e..cd6dfc2 100644
--- a/models/CalendarEvent.ts
+++ b/models/CalendarEvent.ts
@@ -22,7 +22,7 @@ export const postEventBodySchema = z.object({
event: z.object({
title: z.string().max(240),
description: z.string().max(1200).optional().nullable(),
- location: z.string().optional().nullable(),
+ location: z.string().max(240).optional().nullable(),
startDate: dateSchema.required(),
endDate: dateSchema.optional().nullable(),
hidden: z.boolean().optional().nullable(),
diff --git a/server/api/calendars/events/[id].patch.ts b/server/api/calendars/events/[id].patch.ts
index 46ed8c2..ca0aa9a 100644
--- a/server/api/calendars/events/[id].patch.ts
+++ b/server/api/calendars/events/[id].patch.ts
@@ -27,7 +27,7 @@ export default defineEventHandler(async (event) => {
fatal: false,
statusCode: 401,
statusMessage: "Validation Error",
- message: "Erreur de validation du schéma, cela est probablement dû à une erreur utilisateur.",
+ message: "Erreur de validation du schéma, probablement dûe à une erreur utilisateur.",
data: {
errors: bodyError.issues.map(issue => ({
path: issue.path,
diff --git a/server/api/calendars/events/create.post.ts b/server/api/calendars/events/create.post.ts
index 46efe29..c0fe7f0 100644
--- a/server/api/calendars/events/create.post.ts
+++ b/server/api/calendars/events/create.post.ts
@@ -7,14 +7,22 @@ export default defineEventHandler(async (event) => {
const { data: bodyData, error: schemaError } = await readValidatedBody(event, body => postEventBodySchema.safeParse(body))
if (schemaError) {
- throw createError({
+ const error = createError({
cause: "Utilisateur",
fatal: false,
- message: "Le schéma de la requête n'est pas complet ou mal renseigné.",
- data: schemaError.issues,
- statusMessage: "test",
- status: 401,
+ statusCode: 401,
+ statusMessage: "Validation Error",
+ message: "Erreur de validation du schéma, probablement dûe à une erreur utilisateur.",
+ data: {
+ errors: schemaError.issues.map(issue => ({
+ path: issue.path,
+ message: issue.message,
+ code: issue.code
+ }))
+ }
})
+
+ throw error
}
try {
diff --git a/supabase/migrations/202401_init.sql b/supabase/migrations/202401_init.sql
index efb4546..96d19a4 100644
--- a/supabase/migrations/202401_init.sql
+++ b/supabase/migrations/202401_init.sql
@@ -93,7 +93,8 @@ alter table public.calendar_events
add constraint calendar_events_maxlen_check
check (
char_length(title) <= 240 AND
- (description IS NULL OR char_length(description) <= 1200)
+ (description IS NULL OR char_length(description) <= 1200) AND
+ (location IS NULL OR char_length(location) <= 240)
);
-- Link table for events - categories
From 863937bb160d259671ef422b368a0024791025cc Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Mon, 25 Nov 2024 16:33:02 +0100
Subject: [PATCH 4/6] Changed limit and added scrollbars on details
---
components/calendar/CalendarEventDetails.vue | 32 ++++++++++++--------
components/calendar/form/UpdateEvent.vue | 6 +++-
i18n.config.ts | 8 ++---
models/CalendarEvent.ts | 4 +--
supabase/migrations/202401_init.sql | 4 +--
5 files changed, 33 insertions(+), 21 deletions(-)
diff --git a/components/calendar/CalendarEventDetails.vue b/components/calendar/CalendarEventDetails.vue
index eac1413..cbbc47f 100644
--- a/components/calendar/CalendarEventDetails.vue
+++ b/components/calendar/CalendarEventDetails.vue
@@ -75,7 +75,7 @@ function deployDeleteModal() {
>
diff --git a/i18n.config.ts b/i18n.config.ts
index e655bfc..628b124 100644
--- a/i18n.config.ts
+++ b/i18n.config.ts
@@ -142,9 +142,9 @@ export default defineI18nConfig(() => ({
},
editErrors: {
toastTitle: "Event wasn't updated.",
- title_too_big: "Title should be less than 240 characters long.",
+ title_too_big: "Title should be less than 120 characters long.",
description_too_big: "Description should be less than 1200 characters long.",
- location_too_big: "Location should be less than 240 characters long.",
+ location_too_big: "Location should be less than 160 characters long.",
},
deleteDialog: {
title: "Delete this event",
@@ -356,9 +356,9 @@ export default defineI18nConfig(() => ({
},
editErrors: {
toastTitle: "L'évènement n'a pas été modifié",
- title_too_big: "Le titre doit être inférieur à 240 caractères.",
+ title_too_big: "Le titre doit être inférieur à 120 caractères.",
description_too_big: "La description doit être inférieure à 1200 caractères.",
- location_too_big: "La localisation doit être inférieure à 240 caractères.",
+ location_too_big: "La localisation doit être inférieure à 160 caractères.",
},
deleteDialog: {
title: "Supprimer l'évènement",
diff --git a/models/CalendarEvent.ts b/models/CalendarEvent.ts
index cd6dfc2..65562fa 100644
--- a/models/CalendarEvent.ts
+++ b/models/CalendarEvent.ts
@@ -20,9 +20,9 @@ export interface CalendarEvent {
*/
export const postEventBodySchema = z.object({
event: z.object({
- title: z.string().max(240),
+ title: z.string().max(120),
description: z.string().max(1200).optional().nullable(),
- location: z.string().max(240).optional().nullable(),
+ location: z.string().max(160).optional().nullable(),
startDate: dateSchema.required(),
endDate: dateSchema.optional().nullable(),
hidden: z.boolean().optional().nullable(),
diff --git a/supabase/migrations/202401_init.sql b/supabase/migrations/202401_init.sql
index 96d19a4..7916e7c 100644
--- a/supabase/migrations/202401_init.sql
+++ b/supabase/migrations/202401_init.sql
@@ -92,9 +92,9 @@ comment on table public.calendar_events is 'Events linked to a world';
alter table public.calendar_events
add constraint calendar_events_maxlen_check
check (
- char_length(title) <= 240 AND
+ char_length(title) <= 120 AND
(description IS NULL OR char_length(description) <= 1200) AND
- (location IS NULL OR char_length(location) <= 240)
+ (location IS NULL OR char_length(location) <= 160)
);
-- Link table for events - categories
From d774fde1dd091f3c17b8e9aa6adeae4635485852 Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Mon, 25 Nov 2024 17:41:40 +0100
Subject: [PATCH 5/6] Added client side validation for fields
---
components/calendar/form/CreateEvent.vue | 47 +++++++++++++-----
components/calendar/form/UpdateEvent.vue | 63 +++++++++++++++---------
i18n.config.ts | 10 ++++
3 files changed, 87 insertions(+), 33 deletions(-)
diff --git a/components/calendar/form/CreateEvent.vue b/components/calendar/form/CreateEvent.vue
index 3a63e4f..65003ee 100644
--- a/components/calendar/form/CreateEvent.vue
+++ b/components/calendar/form/CreateEvent.vue
@@ -5,6 +5,9 @@ import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhTag } from
const { eventSkeleton, operationInProgress } = storeToRefs(useCalendar())
const { resetSkeleton, submitSkeleton, cancelLatestRequest } = useCalendar()
const popoverOpen = ref(false)
+
+const { t } = useI18n()
+
const isLoading = ref(false)
const formErrors = reactive<{ message: string | null }>({
@@ -88,7 +91,7 @@ function handleCancel() {
:collision-padding="60"
:disable-outside-pointer-events="true"
:trap-focus="true"
- class="pl-3 min-w-96 border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
+ class="pl-3 w-[30rem] max-w-full border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
@escape-key-down="handleClosing"
@focus-outside="handleClosing"
@interact-outside="handleClosing"
@@ -104,8 +107,14 @@ function handleCancel() {
name="new-event-title"
required
:placeholder="$t('entity.calendar.event.title')"
- class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
+ :maxlength="120"
+ pattern="([A-Za-zÀ-ÖØ-öø-ÿ0-9\s\&\-\~]+){3,120}"
+ class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500"
>
+
+
+ {{ t('entity.calendar.event.patterns.title') }}
+
@@ -113,8 +122,13 @@ function handleCancel() {
id="new-event-description"
v-model="eventSkeleton.description"
name="new-event-description"
- :placeholder="$t('entity.addDescription')" class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
+ :placeholder="$t('entity.addDescription')"
+ :maxlength="1200"
+ class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500"
/>
+
+ {{ t('entity.calendar.event.patterns.description') }}
+
@@ -142,7 +156,10 @@ function handleCancel() {
@@ -158,13 +175,21 @@ function handleCancel() {
-
+
+
+
+ {{ t('entity.calendar.event.patterns.location') }}
+
+
diff --git a/components/calendar/form/UpdateEvent.vue b/components/calendar/form/UpdateEvent.vue
index 644e550..3bf6975 100644
--- a/components/calendar/form/UpdateEvent.vue
+++ b/components/calendar/form/UpdateEvent.vue
@@ -97,16 +97,22 @@ function handleCancel() {
-
+
+
+
+ {{ t('entity.calendar.event.patterns.title') }}
+
+
@@ -116,9 +122,12 @@ function handleCancel() {
v-model="eventSkeleton.description"
name="new-event-description"
:placeholder="$t('entity.addDescription')"
- class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
:maxlength="1200"
+ class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600 invalid:border-red-500"
/>
+
+ {{ t('entity.calendar.event.patterns.description') }}
+
@@ -147,7 +156,10 @@ function handleCancel() {
-
+
@@ -157,7 +169,7 @@ function handleCancel() {
-
+
-->
@@ -166,15 +178,22 @@ function handleCancel() {
-
+
+
+
+
+ {{ t('entity.calendar.event.patterns.location') }}
+
+
diff --git a/i18n.config.ts b/i18n.config.ts
index 628b124..c4ff153 100644
--- a/i18n.config.ts
+++ b/i18n.config.ts
@@ -136,6 +136,11 @@ export default defineI18nConfig(() => ({
addLocation: "Add a place",
prevPage: "Previous page with events",
nextPage: "Next page with events",
+ patterns: {
+ title: "Between 3 and 120 caracters, without special symbols, except & - ~ ()",
+ description: "1200 caracters max.",
+ location: "Between 3 and 160 caracters, without special symbols, except & - ~ ()",
+ },
editDialog: {
title: "Edit event",
subtitle: "Update event data",
@@ -350,6 +355,11 @@ export default defineI18nConfig(() => ({
addLocation: "Ajouter un endroit",
prevPage: "Précédente page à évènements",
nextPage: "Prochaine page à évènements",
+ patterns: {
+ title: "Entre 3 et 120 charactères, sans symboles spéciaux, sauf & - ~ ()",
+ description: "Maximum 1200 charactères.",
+ location: "Entre 3 et 160 charactères, sans symboles spéciaux, sauf & - ~ ()",
+ },
editDialog: {
title: "Modifier l'évènement",
subtitle: "Mettre à jour les données de l'évènement",
From 378f07a862c0156d4115edcf0cc23467804c1bc2 Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Mon, 25 Nov 2024 18:09:18 +0100
Subject: [PATCH 6/6] User locale now saves !
---
nuxt.config.ts | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/nuxt.config.ts b/nuxt.config.ts
index f4eea9f..3db37db 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -16,12 +16,21 @@ export default defineNuxtConfig({
i18n: {
vueI18n: "./i18n.config.ts",
+ strategy: "no_prefix",
detectBrowserLanguage: {
useCookie: true,
cookieKey: "tttools_lang",
- redirectOn: "all",
- alwaysRedirect: true,
- }
+ },
+ locales: [
+ {
+ code: "en",
+ name: "English"
+ },
+ {
+ code: "fr",
+ name: "Français"
+ }
+ ]
},
supabase: {