From 21de7bd5b0cd84eda3207d46246b758b4134c35e Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Sun, 22 Sep 2024 11:20:19 +0200 Subject: [PATCH 01/23] Write defaultCreateFormAction function This is meant as a drop-in replacement for most create form actions --- frontend/src/lib/utils/actions.ts | 102 ++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 frontend/src/lib/utils/actions.ts diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts new file mode 100644 index 000000000..505982d65 --- /dev/null +++ b/frontend/src/lib/utils/actions.ts @@ -0,0 +1,102 @@ +import { BASE_API_URL } from '$lib/utils/constants'; +import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; + +import * as m from '$paraglide/messages'; + +import { safeTranslate } from '$lib/utils/i18n'; +import { modelSchema } from '$lib/utils/schemas'; +import { RequestEvent, fail } from '@sveltejs/kit'; +import { setFlash } from 'sveltekit-flash-message/server'; +import { setError, superValidate } from 'sveltekit-superforms'; +import { zod } from 'sveltekit-superforms/adapters'; + +export async function defaultCreateFormAction(event: RequestEvent, urlModel: string) { + const formData = await event.request.formData(); + + if (!formData) { + return fail(400, { form: null }); + } + + const schema = modelSchema(urlModel!); + const form = await superValidate(formData, zod(schema)); + + if (!form.valid) { + console.error(form.errors); + return fail(400, { form: form }); + } + + const endpoint = `${BASE_API_URL}/${urlModel}/`; + const model = getModelInfo(urlModel!); + + const fileFields: Record = Object.fromEntries( + Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) + ); + + Object.keys(fileFields).forEach((key) => { + form.data[key] = undefined; + }); + + const requestInitOptions: RequestInit = { + method: 'POST', + body: JSON.stringify(form.data) + }; + + const res = await event.fetch(endpoint, requestInitOptions); + + if (!res.ok) { + const response: Record = await res.json(); + console.error(response); + if (response.warning) { + setFlash({ type: 'warning', message: response.warning }, event); + return { createForm: form }; + } + if (response.error) { + setFlash({ type: 'error', message: response.error }, event); + return { createForm: form }; + } + Object.entries(response).forEach(([key, value]) => { + setError(form, key, value); + }); + return fail(400, { form: form }); + } + + const createdObject = await res.json(); + + if (fileFields) { + for (const [, file] of Object.entries(fileFields)) { + if (!file) continue; + if (file.size <= 0) continue; + const fileUploadEndpoint = `${BASE_API_URL}/${urlModel}/${createdObject.id}/upload/`; + const fileUploadRequestInitOptions: RequestInit = { + headers: { + 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` + }, + method: 'POST', + body: file + }; + const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); + if (!fileUploadRes.ok) { + const response = await fileUploadRes.json(); + console.error(response); + if (response.non_field_errors) { + setError(form, 'non_field_errors', response.non_field_errors); + } + return fail(400, { form: form }); + } + } + } + + const modelVerboseName: string = urlModel ? urlParamModelVerboseName(urlModel) : ''; + + setFlash( + { + type: 'success', + message: m.successfullyCreatedObject({ + object: safeTranslate(modelVerboseName).toLowerCase() + }) + }, + event + ); + + return { createForm: form }; +} From f31a1a44a1ae542526a5c2b042d81ffc5962ecb0 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Sun, 22 Sep 2024 11:46:57 +0200 Subject: [PATCH 02/23] Write getSuccessMessage, getHTTPMethod functions --- frontend/src/lib/utils/actions.ts | 48 +++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts index 505982d65..b05c1c936 100644 --- a/frontend/src/lib/utils/actions.ts +++ b/frontend/src/lib/utils/actions.ts @@ -5,12 +5,48 @@ import * as m from '$paraglide/messages'; import { safeTranslate } from '$lib/utils/i18n'; import { modelSchema } from '$lib/utils/schemas'; -import { RequestEvent, fail } from '@sveltejs/kit'; +import { type RequestEvent, fail } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -export async function defaultCreateFormAction(event: RequestEvent, urlModel: string) { +type FormAction = 'create' | 'edit'; + +function getHTTPMethod({ + action, + fileFields +}: { + urlModel: string; + action: FormAction; + fileFields: Record; +}) { + if (action === 'create') return 'POST'; + return Object.keys(fileFields).length > 0 ? 'PATCH' : 'PUT'; +} + +function getSuccessMessage({ action, urlModel }: { action: FormAction; urlModel: string }) { + const modelVerboseName: string = urlModel ? urlParamModelVerboseName(urlModel) : ''; + if (action === 'create') { + return m.successfullyCreatedObject({ + object: safeTranslate(modelVerboseName).toLowerCase() + }); + } + if (action === 'edit') { + return m.successfullyUpdatedObject({ + object: safeTranslate(modelVerboseName).toLowerCase() + }); + } +} + +export async function defaultWriteFormAction({ + event, + urlModel, + action +}: { + event: RequestEvent; + urlModel: string; + action: FormAction; +}) { const formData = await event.request.formData(); if (!formData) { @@ -37,7 +73,7 @@ export async function defaultCreateFormAction(event: RequestEvent, urlModel: str }); const requestInitOptions: RequestInit = { - method: 'POST', + method: getHTTPMethod({ action, fileFields }), body: JSON.stringify(form.data) }; @@ -86,14 +122,10 @@ export async function defaultCreateFormAction(event: RequestEvent, urlModel: str } } - const modelVerboseName: string = urlModel ? urlParamModelVerboseName(urlModel) : ''; - setFlash( { type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) + message: getSuccessMessage({ urlModel, action }) }, event ); From b4b7bf8bbb365d791bc947292d79ab9dc5d64c5f Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Sun, 22 Sep 2024 12:35:19 +0200 Subject: [PATCH 03/23] Use defaultWriteFormAction for default model route --- .../[model=urlmodel]/+page.server.ts | 103 +--------------- .../[id=uuid]/+page.server.ts | 111 +----------------- 2 files changed, 8 insertions(+), 206 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts index 5ce5854e3..f31609ab5 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts @@ -16,6 +16,7 @@ import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import type { PageServerLoad } from './$types'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const schema = z.object({ id: z.string().uuid() }); @@ -67,107 +68,7 @@ export const load: PageServerLoad = async ({ params, fetch }) => { export const actions: Actions = { create: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema(event.params.model!); - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.error(form.errors); - return fail(400, { form: form }); - } - - const endpoint = `${BASE_API_URL}/${event.params.model}/`; - - const model = getModelInfo(event.params.model!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - form.data[key] = undefined; - }); - - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (!file) continue; - if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${event.params.model}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName: string = event.params.model - ? urlParamModelVerboseName(event.params.model) - : ''; - // TODO: reference newly created object - if (modelVerboseName === 'User') { - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - } - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - return { createForm: form }; + return defaultWriteFormAction({ event, urlModel: event.params.model!, action: 'create' }); }, delete: async (event) => { const formData = await event.request.formData(); diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts index aa69cf586..f31ca9350 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts @@ -1,121 +1,22 @@ import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; +import { urlParamModelVerboseName } from '$lib/utils/crud'; import { localItems, toCamelCase } from '$lib/utils/locales'; import * as m from '$paraglide/messages'; -import { modelSchema } from '$lib/utils/schemas'; import { fail, type Actions } from '@sveltejs/kit'; -import { setFlash } from 'sveltekit-flash-message/server'; import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import { safeTranslate } from '$lib/utils/i18n'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { create: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema(formData.get('urlmodel') as string); - const urlModel = formData.get('urlmodel'); - - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.log(form.errors); - return fail(400, { form }); - } - - const endpoint = `${BASE_API_URL}/${urlModel}/`; - - const model = getModelInfo(urlModel!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - form.data[key] = undefined; - }); - - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (file.size <= 0) { - continue; - } - const fileUploadEndpoint = `${BASE_API_URL}/${urlModel}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName: string = urlParamModelVerboseName(urlModel); - - if (modelVerboseName === 'User') { - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - } - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - return { createForm: form }; + const form = await superValidate(event.request); + const urlModel = form.data.urlmodel as string; + console.log('URLMODEL', urlModel); + return defaultWriteFormAction({ event, urlModel, action: 'create' }); }, delete: async ({ request, fetch, params }) => { const formData = await request.formData(); From 196c418e12434e08eb13bef2facce2354fc65af1 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Sun, 22 Sep 2024 12:35:44 +0200 Subject: [PATCH 04/23] Use defaultWriteFormAction for nested create forms in default model route --- .../[id=uuid]/+page.server.ts | 111 +----------------- .../[id=uuid]/edit/+page.server.ts | 104 +--------------- 2 files changed, 9 insertions(+), 206 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts index aa69cf586..113fb8e11 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts @@ -1,121 +1,22 @@ import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; +import { urlParamModelVerboseName } from '$lib/utils/crud'; import { localItems, toCamelCase } from '$lib/utils/locales'; import * as m from '$paraglide/messages'; -import { modelSchema } from '$lib/utils/schemas'; import { fail, type Actions } from '@sveltejs/kit'; -import { setFlash } from 'sveltekit-flash-message/server'; import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import { safeTranslate } from '$lib/utils/i18n'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { create: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema(formData.get('urlmodel') as string); - const urlModel = formData.get('urlmodel'); - - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.log(form.errors); - return fail(400, { form }); - } - - const endpoint = `${BASE_API_URL}/${urlModel}/`; - - const model = getModelInfo(urlModel!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - form.data[key] = undefined; - }); - - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (file.size <= 0) { - continue; - } - const fileUploadEndpoint = `${BASE_API_URL}/${urlModel}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName: string = urlParamModelVerboseName(urlModel); - - if (modelVerboseName === 'User') { - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - } - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - return { createForm: form }; + const request = event.request.clone(); + const formData = await request.formData(); + const urlModel = formData.get('urlmodel') as string; + return defaultWriteFormAction({ event, urlModel, action: 'create' }); }, delete: async ({ request, fetch, params }) => { const formData = await request.formData(); diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/edit/+page.server.ts index 76e7f09fb..343e97120 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/edit/+page.server.ts @@ -1,107 +1,9 @@ -import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; -import { getSecureRedirect } from '$lib/utils/helpers'; -import { safeTranslate } from '$lib/utils/i18n'; -import { modelSchema } from '$lib/utils/schemas'; -import { fail, redirect, type Actions } from '@sveltejs/kit'; -import { setFlash } from 'sveltekit-flash-message/server'; -import { setError, superValidate } from 'sveltekit-superforms'; +import { type Actions } from '@sveltejs/kit'; -import * as m from '$paraglide/messages'; -import { zod } from 'sveltekit-superforms/adapters'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { default: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema(event.params.model!); - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.error(form.errors); - return fail(400, { form: form }); - } - - const endpoint = `${BASE_API_URL}/${event.params.model}/${event.params.id}/`; - - const model = getModelInfo(event.params.model!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - delete form.data[key]; - }); - - const requestInitOptions: RequestInit = { - method: fileFields.length > 0 ? 'PATCH' : 'PUT', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (!file) continue; - if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${event.params.model}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName: string = urlParamModelVerboseName(event.params.model!); - setFlash( - { - type: 'success', - message: m.successfullyUpdatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - redirect( - 302, - getSecureRedirect(event.url.searchParams.get('next')) ?? - `/${event.params.model}/${event.params.id}` - ); + return defaultWriteFormAction({ event, urlModel: event.params.model!, action: 'edit' }); } }; From 9b3c7ac73242bd588e97b9dbaf0ab4d127cc492c Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Sun, 22 Sep 2024 12:37:08 +0200 Subject: [PATCH 05/23] Use defaultWriteFormAction for edit forms in default model route --- .../[id=uuid]/edit/+page.server.ts | 104 +----------------- 1 file changed, 3 insertions(+), 101 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/edit/+page.server.ts index 76e7f09fb..343e97120 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/edit/+page.server.ts @@ -1,107 +1,9 @@ -import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; -import { getSecureRedirect } from '$lib/utils/helpers'; -import { safeTranslate } from '$lib/utils/i18n'; -import { modelSchema } from '$lib/utils/schemas'; -import { fail, redirect, type Actions } from '@sveltejs/kit'; -import { setFlash } from 'sveltekit-flash-message/server'; -import { setError, superValidate } from 'sveltekit-superforms'; +import { type Actions } from '@sveltejs/kit'; -import * as m from '$paraglide/messages'; -import { zod } from 'sveltekit-superforms/adapters'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { default: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema(event.params.model!); - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.error(form.errors); - return fail(400, { form: form }); - } - - const endpoint = `${BASE_API_URL}/${event.params.model}/${event.params.id}/`; - - const model = getModelInfo(event.params.model!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - delete form.data[key]; - }); - - const requestInitOptions: RequestInit = { - method: fileFields.length > 0 ? 'PATCH' : 'PUT', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (!file) continue; - if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${event.params.model}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName: string = urlParamModelVerboseName(event.params.model!); - setFlash( - { - type: 'success', - message: m.successfullyUpdatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - redirect( - 302, - getSecureRedirect(event.url.searchParams.get('next')) ?? - `/${event.params.model}/${event.params.id}` - ); + return defaultWriteFormAction({ event, urlModel: event.params.model!, action: 'edit' }); } }; From 2130f380eb427a8b0c72870dfb28024e9c5b26d7 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 14:30:50 +0200 Subject: [PATCH 06/23] Fix edit action --- frontend/src/lib/utils/actions.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts index b05c1c936..2231408c1 100644 --- a/frontend/src/lib/utils/actions.ts +++ b/frontend/src/lib/utils/actions.ts @@ -5,10 +5,11 @@ import * as m from '$paraglide/messages'; import { safeTranslate } from '$lib/utils/i18n'; import { modelSchema } from '$lib/utils/schemas'; -import { type RequestEvent, fail } from '@sveltejs/kit'; +import { type RequestEvent, fail, redirect } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; +import { getSecureRedirect } from './helpers'; type FormAction = 'create' | 'edit'; @@ -38,6 +39,22 @@ function getSuccessMessage({ action, urlModel }: { action: FormAction; urlModel: } } +function getEndpoint({ + action, + urlModel, + event +}: { + action: FormAction; + urlModel: string; + event: RequestEvent; +}) { + if (action === 'create') { + return `${BASE_API_URL}/${urlModel}/`; + } + const id = event.params.id; + return `${BASE_API_URL}/${urlModel}/${id}/`; +} + export async function defaultWriteFormAction({ event, urlModel, @@ -61,7 +78,7 @@ export async function defaultWriteFormAction({ return fail(400, { form: form }); } - const endpoint = `${BASE_API_URL}/${urlModel}/`; + const endpoint = getEndpoint({ action, urlModel, event }); const model = getModelInfo(urlModel!); const fileFields: Record = Object.fromEntries( @@ -125,10 +142,13 @@ export async function defaultWriteFormAction({ setFlash( { type: 'success', - message: getSuccessMessage({ urlModel, action }) + message: getSuccessMessage({ urlModel, action }) as string }, event ); + const next = getSecureRedirect(event.url.searchParams.get('next')); + if (next) redirect(302, next); + return { createForm: form }; } From 9bb7305a73af2081e6e1911575eb0b36e936b2d1 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 15:16:50 +0200 Subject: [PATCH 07/23] Write nestedWriteFormAction function Plus some quality improvements --- frontend/src/lib/utils/actions.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts index 2231408c1..5271de501 100644 --- a/frontend/src/lib/utils/actions.ts +++ b/frontend/src/lib/utils/actions.ts @@ -17,7 +17,6 @@ function getHTTPMethod({ action, fileFields }: { - urlModel: string; action: FormAction; fileFields: Record; }) { @@ -58,11 +57,13 @@ function getEndpoint({ export async function defaultWriteFormAction({ event, urlModel, - action + action, + doRedirect = true }: { event: RequestEvent; urlModel: string; action: FormAction; + doRedirect?: boolean; }) { const formData = await event.request.formData(); @@ -97,7 +98,7 @@ export async function defaultWriteFormAction({ const res = await event.fetch(endpoint, requestInitOptions); if (!res.ok) { - const response: Record = await res.json(); + const response: Record = await res.json(); console.error(response); if (response.warning) { setFlash({ type: 'warning', message: response.warning }, event); @@ -148,7 +149,20 @@ export async function defaultWriteFormAction({ ); const next = getSecureRedirect(event.url.searchParams.get('next')); - if (next) redirect(302, next); + if (next && doRedirect) redirect(302, next); return { createForm: form }; } + +export async function nestedWriteFormAction({ + event, + action +}: { + event: RequestEvent; + action: FormAction; +}) { + const request = event.request.clone(); + const formData = await request.formData(); + const urlModel = formData.get('urlmodel') as string; + return defaultWriteFormAction({ event, urlModel, action, doRedirect: false }); +} From 47845af22165f4fb2f235644a68e1fd15c0211bd Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 15:17:31 +0200 Subject: [PATCH 08/23] Use nestedWriteForm actions for nested model object creation --- .../[id=uuid]/+page.server.ts | 7 +- .../[id=uuid]/edit/+page.server.ts | 89 +----------------- .../[id=uuid]/+page.server.ts | 42 +-------- .../[id=uuid]/+page.server.ts | 7 +- .../[id=uuid]/+page.server.ts | 42 +-------- .../[id=uuid]/table-mode/+page.server.ts | 91 +------------------ 6 files changed, 14 insertions(+), 264 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts index 113fb8e11..34222ac07 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts @@ -9,14 +9,11 @@ import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import { safeTranslate } from '$lib/utils/i18n'; -import { defaultWriteFormAction } from '$lib/utils/actions'; +import { nestedWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { create: async (event) => { - const request = event.request.clone(); - const formData = await request.formData(); - const urlModel = formData.get('urlmodel') as string; - return defaultWriteFormAction({ event, urlModel, action: 'create' }); + return nestedWriteFormAction({ event, action: 'create' }); }, delete: async ({ request, fetch, params }) => { const formData = await request.formData(); diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts index b4243d07c..1924ac189 100644 --- a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts @@ -13,6 +13,7 @@ import { setFlash } from 'sveltekit-flash-message/server'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import type { PageServerLoad } from './$types'; +import { nestedWriteFormAction } from '$lib/utils/actions'; export const load = (async ({ fetch, params }) => { const URLModel = 'requirement-assessments'; @@ -325,92 +326,6 @@ export const actions: Actions = { return { form }; }, createEvidence: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema('evidences'); - const form = await superValidate(formData, zod(schema)); - if (!form.valid) { - console.error(form.errors); - return fail(400, { form: form }); - } - const urlModel = 'evidences'; - const endpoint = `${BASE_API_URL}/${urlModel}/`; - - const model = getModelInfo(urlModel!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - form.data[key] = undefined; - }); - - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (!file) continue; - if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${'evidences'}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName = 'evidences'; - // TODO: reference newly created object - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: localItems()[toCamelCase(modelVerboseName)].toLowerCase() - }) - }, - event - ); - return { createForm: form }; + return nestedWriteFormAction({ event, action: 'create' }); } }; diff --git a/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts index c10947470..f9292dbca 100644 --- a/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts @@ -11,47 +11,11 @@ import { message, setError, superValidate } from 'sveltekit-superforms'; import { z } from 'zod'; import { zod } from 'sveltekit-superforms/adapters'; import { setFlash } from 'sveltekit-flash-message/server'; +import { nestedWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { - create: async ({ request, fetch }) => { - const formData = await request.formData(); - - const schema = modelSchema(formData.get('urlmodel') as string); - const urlModel = formData.get('urlmodel'); - - const createForm = await superValidate(formData, zod(schema)); - - const endpoint = `${BASE_API_URL}/${urlModel}/`; - - if (!createForm.valid) { - console.log(createForm.errors); - return fail(400, { form: createForm }); - } - - if (formData) { - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(createForm.data) - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.non_field_errors) { - setError(createForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: createForm }); - } - const model: string = urlParamModelVerboseName(urlModel); - // TODO: reference newly created object - return message( - createForm, - m.successfullyCreatedObject({ - object: localItems()[toCamelCase(model.toLowerCase())].toLowerCase() - }) - ); - } - return { createForm }; + create: async (event) => { + return nestedWriteFormAction({ event, action: 'create' }); }, delete: async ({ request, fetch, params }) => { const formData = await request.formData(); diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts index f31ca9350..34222ac07 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts @@ -9,14 +9,11 @@ import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import { safeTranslate } from '$lib/utils/i18n'; -import { defaultWriteFormAction } from '$lib/utils/actions'; +import { nestedWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { create: async (event) => { - const form = await superValidate(event.request); - const urlModel = form.data.urlmodel as string; - console.log('URLMODEL', urlModel); - return defaultWriteFormAction({ event, urlModel, action: 'create' }); + return nestedWriteFormAction({ event, action: 'create' }); }, delete: async ({ request, fetch, params }) => { const formData = await request.formData(); diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts index 6b3e71a3c..fa7e305f9 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts @@ -7,6 +7,7 @@ import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; import { fail, type Actions } from '@sveltejs/kit'; import * as m from '$paraglide/messages'; import { localItems, toCamelCase } from '$lib/utils/locales'; +import { nestedWriteFormAction } from '$lib/utils/actions'; export const load = (async ({ fetch, params }) => { const URLModel = 'compliance-assessments'; @@ -96,44 +97,7 @@ export const load = (async ({ fetch, params }) => { }) satisfies PageServerLoad; export const actions: Actions = { - create: async ({ request, fetch }) => { - const formData = await request.formData(); - - const schema = modelSchema(formData.get('urlmodel') as string); - const urlModel = formData.get('urlmodel'); - - const createForm = await superValidate(formData, zod(schema)); - - const endpoint = `${BASE_API_URL}/${urlModel}/`; - - if (!createForm.valid) { - console.log(createForm.errors); - return fail(400, { form: createForm }); - } - - if (formData) { - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(createForm.data) - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.non_field_errors) { - setError(createForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: createForm }); - } - const model: string = urlParamModelVerboseName(urlModel); - // TODO: reference newly created object - return message( - createForm, - m.successfullyCreatedObject({ - object: localItems()[toCamelCase(model.toLowerCase())].toLowerCase() - }) - ); - } - return { createForm }; + create: async (event) => { + return nestedWriteFormAction({ event, action: 'create' }); } }; diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts index b376023de..8d6d692b3 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts @@ -9,6 +9,7 @@ import { setFlash } from 'sveltekit-flash-message/server'; import * as m from '$paraglide/messages'; import { z } from 'zod'; import { safeTranslate } from '$lib/utils/i18n'; +import { nestedWriteFormAction } from '$lib/utils/actions'; export const load = (async ({ fetch, params }) => { const URLModel = 'compliance-assessments'; @@ -90,95 +91,7 @@ export const actions: Actions = { return { status: res.status, body: await res.json() }; }, createEvidence: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema('evidences'); - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.error(form.errors); - return fail(400, { form: form }); - } - - const endpoint = `${BASE_API_URL}/evidences/`; - - const model = getModelInfo('evidences'); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - form.data[key] = undefined; - }); - - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - if (response[0]) { - setError(form, 'non_field_errors', m.nameDuplicate()); - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdEvidence = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (!file) continue; - if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${'evidences'}/${createdEvidence.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: m.evidence().toLowerCase() - }) - }, - event - ); - return { createForm: form, createdEvidence }; + return nestedWriteFormAction({ event, action: 'create' }); }, deleteEvidence: async (event) => { const formData = await event.request.formData(); From 9de6d4e791732ca25973a3df3487069c2a23c1b6 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 15:17:45 +0200 Subject: [PATCH 09/23] Generalize defaultWriteAction pattern --- .../[id=uuid]/edit/+page.server.ts | 43 +------ .../+page.server.ts | 105 +----------------- 2 files changed, 7 insertions(+), 141 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts index 9b45102e5..6f5076e76 100644 --- a/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts @@ -11,6 +11,7 @@ import { fail, redirect, type Actions } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; import * as m from '$paraglide/messages'; import { zod } from 'sveltekit-superforms/adapters'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const URLModel = 'risk-scenarios'; @@ -197,47 +198,7 @@ export const load: PageServerLoad = async ({ params, fetch }) => { export const actions: Actions = { updateRiskScenario: async (event) => { - const URLModel = 'risk-scenarios'; - const schema = modelSchema(URLModel); - const endpoint = `${BASE_API_URL}/${URLModel}/${event.params.id}/`; - const form = await superValidate(event.request, zod(schema)); - - if (!form.valid) { - console.log(form.errors); - return fail(400, { form: form }); - } - - const requestInitOptions: RequestInit = { - method: 'PUT', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error('server response:', response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const modelVerboseName: string = urlParamModelVerboseName(URLModel); - setFlash( - { - type: 'success', - message: m.successfullyUpdatedObject({ object: modelVerboseName }) - }, - event - ); - redirect( - 302, - event.url.searchParams.get('/updateRiskScenario') ?? `/risk-scenarios/${event.params.id}` - ); + return defaultWriteFormAction({ event, urlModel: event.params.model!, action: 'edit' }); }, createAppliedControl: async (event) => { const URLModel = 'applied-controls'; diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts index 5ce5854e3..f16b39ff6 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts @@ -16,6 +16,7 @@ import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import type { PageServerLoad } from './$types'; +import { defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const schema = z.object({ id: z.string().uuid() }); @@ -67,107 +68,11 @@ export const load: PageServerLoad = async ({ params, fetch }) => { export const actions: Actions = { create: async (event) => { - const formData = await event.request.formData(); - - if (!formData) { - return fail(400, { form: null }); - } - - const schema = modelSchema(event.params.model!); - const form = await superValidate(formData, zod(schema)); - - if (!form.valid) { - console.error(form.errors); - return fail(400, { form: form }); - } - - const endpoint = `${BASE_API_URL}/${event.params.model}/`; - - const model = getModelInfo(event.params.model!); - - const fileFields: Record = Object.fromEntries( - Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); - - Object.keys(fileFields).forEach((key) => { - form.data[key] = undefined; + return defaultWriteFormAction({ + event, + urlModel: event.params.model as string, + action: 'create' }); - - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(form.data) - }; - - const res = await event.fetch(endpoint, requestInitOptions); - - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } - - const createdObject = await res.json(); - - if (fileFields) { - for (const [, file] of Object.entries(fileFields)) { - if (!file) continue; - if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${event.params.model}/${createdObject.id}/upload/`; - const fileUploadRequestInitOptions: RequestInit = { - headers: { - 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` - }, - method: 'POST', - body: file - }; - const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } - } - } - - const modelVerboseName: string = event.params.model - ? urlParamModelVerboseName(event.params.model) - : ''; - // TODO: reference newly created object - if (modelVerboseName === 'User') { - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - } - setFlash( - { - type: 'success', - message: m.successfullyCreatedObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - event - ); - return { createForm: form }; }, delete: async (event) => { const formData = await event.request.formData(); From 86b62311ad678aa41a7eb0cb305f7708e794b514 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 15:38:54 +0200 Subject: [PATCH 10/23] Write delete form actions --- frontend/src/lib/utils/actions.ts | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts index 5271de501..2e40a460b 100644 --- a/frontend/src/lib/utils/actions.ts +++ b/frontend/src/lib/utils/actions.ts @@ -10,6 +10,7 @@ import { setFlash } from 'sveltekit-flash-message/server'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { getSecureRedirect } from './helpers'; +import { z } from 'zod'; type FormAction = 'create' | 'edit'; @@ -166,3 +167,61 @@ export async function nestedWriteFormAction({ const urlModel = formData.get('urlmodel') as string; return defaultWriteFormAction({ event, urlModel, action, doRedirect: false }); } + +export async function defaultDeleteFormAction({ + event, + urlModel +}: { + event: RequestEvent; + urlModel: string; +}) { + const formData = await event.request.formData(); + const schema = z.object({ id: z.string().uuid() }); + const deleteForm = await superValidate(formData, zod(schema)); + + const id = deleteForm.data.id; + const endpoint = `${BASE_API_URL}/${urlModel}/${id}/`; + + if (!deleteForm.valid) { + console.log(deleteForm.errors); + return fail(400, { form: deleteForm }); + } + + if (formData.has('delete')) { + const requestInitOptions: RequestInit = { + method: 'DELETE' + }; + const res = await event.fetch(endpoint, requestInitOptions); + if (!res.ok) { + const response = await res.json(); + console.log(response); + if (response.error) { + setFlash({ type: 'error', message: safeTranslate(response.error) }, event); + return fail(403, { form: deleteForm }); + } + if (response.non_field_errors) { + setError(deleteForm, 'non_field_errors', response.non_field_errors); + } + return fail(400, { form: deleteForm }); + } + const model: string = urlParamModelVerboseName(urlModel!); + setFlash( + { + type: 'success', + message: m.successfullyDeletedObject({ + object: safeTranslate(model).toLowerCase() + }) + }, + event + ); + } + + return { deleteForm }; +} + +export async function nestedDeleteFormAction({ event }: { event: RequestEvent }) { + const request = event.request.clone(); + const formData = await request.formData(); + const urlModel = formData.get('urlmodel') as string; + return defaultDeleteFormAction({ event, urlModel }); +} From 51deeb4d25b85ebe11923dfbfa30ab8b861ead3a Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 15:41:03 +0200 Subject: [PATCH 11/23] Generalize deleteFormAction pattern --- .../[model=urlmodel]/+page.server.ts | 45 +----------------- .../[id=uuid]/+page.server.ts | 44 ++--------------- .../(internal)/libraries/+page.server.ts | 33 +------------ .../[id=uuid]/+page.server.ts | 47 +++---------------- .../+page.server.ts | 45 +----------------- .../[id=uuid]/+page.server.ts | 41 ++-------------- 6 files changed, 19 insertions(+), 236 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts index f31609ab5..7ff5d5841 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts @@ -16,7 +16,7 @@ import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import type { PageServerLoad } from './$types'; -import { defaultWriteFormAction } from '$lib/utils/actions'; +import { defaultDeleteFormAction, defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const schema = z.object({ id: z.string().uuid() }); @@ -71,47 +71,6 @@ export const actions: Actions = { return defaultWriteFormAction({ event, urlModel: event.params.model!, action: 'create' }); }, delete: async (event) => { - const formData = await event.request.formData(); - const schema = z.object({ id: z.string().uuid() }); - const deleteForm = await superValidate(formData, zod(schema)); - - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/${event.params.model}/${id}/`; - - if (!deleteForm.valid) { - console.log(deleteForm.errors); - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.error) { - setFlash({ type: 'error', message: localItems()[response.error] }, event); - return fail(403, { form: deleteForm }); - } - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - const model: string = urlParamModelVerboseName(event.params.model!); - // TODO: reference object by name instead of id - setFlash( - { - type: 'success', - message: m.successfullyDeletedObject({ - object: safeTranslate(model).toLowerCase() - }) - }, - event - ); - } - return { deleteForm }; + return defaultDeleteFormAction({ event, urlModel: event.params.model! }); } }; diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts index 85295a896..a7ed99579 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/[id=uuid]/+page.server.ts @@ -1,5 +1,5 @@ import { BASE_API_URL } from '$lib/utils/constants'; -import { urlParamModelVerboseName } from '$lib/utils/crud'; +import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; import { localItems, toCamelCase } from '$lib/utils/locales'; import * as m from '$paraglide/messages'; @@ -8,8 +8,7 @@ import { fail, type Actions } from '@sveltejs/kit'; import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; -import { safeTranslate } from '$lib/utils/i18n'; -import { nestedWriteFormAction } from '$lib/utils/actions'; +import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; import { loadDetail } from '$lib/utils/load'; import type { PageServerLoad } from './$types'; @@ -22,43 +21,8 @@ export const actions: Actions = { create: async (event) => { return nestedWriteFormAction({ event, action: 'create' }); }, - delete: async ({ request, fetch, params }) => { - const formData = await request.formData(); - const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const deleteForm = await superValidate(formData, zod(schema)); - - const urlmodel = deleteForm.data.urlmodel; - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/`; - - if (!deleteForm.valid) { - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - console.log(params); - const model: string = urlParamModelVerboseName(urlmodel); - // TODO: reference object by name instead of id - return message( - deleteForm, - m.successfullyDeletedObject({ - object: safeTranslate(model).toLowerCase() - }) - ); - } - return { deleteForm }; + delete: async (event) => { + return nestedDeleteFormAction({ event }); }, reject: async ({ request, fetch, params }) => { const formData = await request.formData(); diff --git a/frontend/src/routes/(app)/(internal)/libraries/+page.server.ts b/frontend/src/routes/(app)/(internal)/libraries/+page.server.ts index 764ca1b85..e6b9b557b 100644 --- a/frontend/src/routes/(app)/(internal)/libraries/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/libraries/+page.server.ts @@ -12,6 +12,7 @@ import { listViewFields } from '$lib/utils/table'; import type { Library } from '$lib/utils/types'; import * as m from '$paraglide/messages'; import { localItems } from '$lib/utils/locales'; +import { nestedDeleteFormAction } from '$lib/utils/actions'; export const load = (async ({ fetch }) => { const stored_libraries_endpoint = `${BASE_API_URL}/stored-libraries/`; @@ -109,36 +110,6 @@ export const actions: Actions = { } }, delete: async (event) => { - const formData = await event.request.formData(); - const schema = z.object({ id: z.string().regex(URN_REGEX) }); - const deleteForm = await superValidate(formData, zod(schema)); - - const URLModel = formData.get('urlmodel'); - - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/${URLModel}/${id}/`; - - if (!deleteForm.valid) { - console.error(deleteForm.errors); - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.error(response); - setFlash({ type: 'error', message: `${response}` }, event); - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - setFlash({ type: 'success', message: m.successfullyDeletedLibrary() }, event); - } - return { deleteForm }; + return nestedDeleteFormAction({ event }); } }; diff --git a/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts index f9292dbca..d91a4887a 100644 --- a/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts @@ -1,57 +1,22 @@ -import { safeTranslate } from '$lib/utils/i18n'; import { BASE_API_URL } from '$lib/utils/constants'; import { urlParamModelVerboseName } from '$lib/utils/crud'; +import { safeTranslate } from '$lib/utils/i18n'; import * as m from '$paraglide/messages'; -import { localItems, toCamelCase } from '$lib/utils/locales'; +import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; import { modelSchema } from '$lib/utils/schemas'; import { fail, type Actions } from '@sveltejs/kit'; -import { message, setError, superValidate } from 'sveltekit-superforms'; -import { z } from 'zod'; -import { zod } from 'sveltekit-superforms/adapters'; import { setFlash } from 'sveltekit-flash-message/server'; -import { nestedWriteFormAction } from '$lib/utils/actions'; +import { setError, superValidate } from 'sveltekit-superforms'; +import { zod } from 'sveltekit-superforms/adapters'; export const actions: Actions = { create: async (event) => { return nestedWriteFormAction({ event, action: 'create' }); }, - delete: async ({ request, fetch, params }) => { - const formData = await request.formData(); - const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const deleteForm = await superValidate(formData, zod(schema)); - - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/risk-scenarios/${id}/`; - - if (!deleteForm.valid) { - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - const model: string = urlParamModelVerboseName(params.model!); - // TODO: reference object by name instead of id - return message( - deleteForm, - m.successfullyDeletedObject({ - object: localItems()[toCamelCase(model.toLowerCase())].toLowerCase() - }) - ); - } - return { deleteForm }; + delete: async (event) => { + return nestedDeleteFormAction({ event }); }, duplicate: async ({ request, fetch, params, cookies }) => { const formData = await request.formData(); diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts index f16b39ff6..fdfe3c5e5 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/+page.server.ts @@ -16,7 +16,7 @@ import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import type { PageServerLoad } from './$types'; -import { defaultWriteFormAction } from '$lib/utils/actions'; +import { defaultDeleteFormAction, defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const schema = z.object({ id: z.string().uuid() }); @@ -75,47 +75,6 @@ export const actions: Actions = { }); }, delete: async (event) => { - const formData = await event.request.formData(); - const schema = z.object({ id: z.string().uuid() }); - const deleteForm = await superValidate(formData, zod(schema)); - - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/${event.params.model}/${id}/`; - - if (!deleteForm.valid) { - console.log(deleteForm.errors); - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.error) { - setFlash({ type: 'error', message: localItems()[response.error] }, event); - return fail(403, { form: deleteForm }); - } - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - const model: string = urlParamModelVerboseName(event.params.model!); - // TODO: reference object by name instead of id - setFlash( - { - type: 'success', - message: m.successfullyDeletedObject({ - object: safeTranslate(model).toLowerCase() - }) - }, - event - ); - } - return { deleteForm }; + return defaultDeleteFormAction({ event, urlModel: event.params.model as string }); } }; diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts index 34222ac07..8732604a1 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts @@ -9,49 +9,14 @@ import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import { safeTranslate } from '$lib/utils/i18n'; -import { nestedWriteFormAction } from '$lib/utils/actions'; +import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { create: async (event) => { return nestedWriteFormAction({ event, action: 'create' }); }, - delete: async ({ request, fetch, params }) => { - const formData = await request.formData(); - const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const deleteForm = await superValidate(formData, zod(schema)); - - const urlmodel = deleteForm.data.urlmodel; - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/`; - - if (!deleteForm.valid) { - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - console.log(params); - const model: string = urlParamModelVerboseName(urlmodel); - // TODO: reference object by name instead of id - return message( - deleteForm, - m.successfullyDeletedObject({ - object: safeTranslate(model).toLowerCase() - }) - ); - } - return { deleteForm }; + delete: async (event) => { + return nestedDeleteFormAction({ event }); }, reject: async ({ request, fetch, params }) => { const formData = await request.formData(); From b3701f0a8b976526cfd4a8f0c9d0297a9cba3727 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 20:20:05 +0200 Subject: [PATCH 12/23] chore: Remove unused imports --- .../[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts index 8732604a1..6592300a6 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts @@ -4,12 +4,11 @@ import { urlParamModelVerboseName } from '$lib/utils/crud'; import { localItems, toCamelCase } from '$lib/utils/locales'; import * as m from '$paraglide/messages'; +import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; import { fail, type Actions } from '@sveltejs/kit'; import { message, setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; -import { safeTranslate } from '$lib/utils/i18n'; -import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; export const actions: Actions = { create: async (event) => { From 3aeaea9b1de4ca20cf7287b6102cec4cb419738f Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Mon, 23 Sep 2024 21:05:49 +0200 Subject: [PATCH 13/23] Fix risk scenario update form action --- .../(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts index 6f5076e76..748024abf 100644 --- a/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/risk-scenarios/[id=uuid]/edit/+page.server.ts @@ -198,7 +198,7 @@ export const load: PageServerLoad = async ({ params, fetch }) => { export const actions: Actions = { updateRiskScenario: async (event) => { - return defaultWriteFormAction({ event, urlModel: event.params.model!, action: 'edit' }); + return defaultWriteFormAction({ event, urlModel: 'risk-scenarios', action: 'edit' }); }, createAppliedControl: async (event) => { const URLModel = 'applied-controls'; From 6e196e53987687a6d35cccd7e54f1e910c872b35 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:08:04 +0200 Subject: [PATCH 14/23] Write handleErrorResponse function --- frontend/src/lib/utils/actions.ts | 44 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts index 2e40a460b..7c782a05e 100644 --- a/frontend/src/lib/utils/actions.ts +++ b/frontend/src/lib/utils/actions.ts @@ -7,7 +7,7 @@ import { safeTranslate } from '$lib/utils/i18n'; import { modelSchema } from '$lib/utils/schemas'; import { type RequestEvent, fail, redirect } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; -import { setError, superValidate } from 'sveltekit-superforms'; +import { setError, superValidate, type SuperValidated } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { getSecureRedirect } from './helpers'; import { z } from 'zod'; @@ -55,6 +55,31 @@ function getEndpoint({ return `${BASE_API_URL}/${urlModel}/${id}/`; } +export async function handleErrorResponse({ + event, + response, + form +}: { + event: RequestEvent; + response: Response; + form: SuperValidated; +}) { + const res: Record = await response.json(); + console.error(res); + if (res.warning) { + setFlash({ type: 'warning', message: res.warning }, event); + return { form }; + } + if (res.error) { + setFlash({ type: 'error', message: res.error }, event); + return { form }; + } + Object.entries(res).forEach(([key, value]) => { + setError(form, key, value); + }); + return fail(400, { form }); +} + export async function defaultWriteFormAction({ event, urlModel, @@ -98,22 +123,7 @@ export async function defaultWriteFormAction({ const res = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { createForm: form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { createForm: form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form: form }); - } + if (!res.ok) return await handleErrorResponse({ event, response: res, form }); const createdObject = await res.json(); From d70afec71c37c3e2bf605dda5a035a02af01fdef Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:10:27 +0200 Subject: [PATCH 15/23] chore: Remove unused imports --- .../(internal)/[model=urlmodel]/+page.server.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts index 7ff5d5841..1f9fc19d0 100644 --- a/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/[model=urlmodel]/+page.server.ts @@ -1,22 +1,17 @@ +import { defaultDeleteFormAction, defaultWriteFormAction } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; import { getModelInfo, urlParamModelForeignKeyFields, - urlParamModelSelectFields, - urlParamModelVerboseName + urlParamModelSelectFields } from '$lib/utils/crud'; -import { safeTranslate } from '$lib/utils/i18n'; -import { localItems } from '$lib/utils/locales'; import { modelSchema } from '$lib/utils/schemas'; import type { ModelInfo } from '$lib/utils/types'; -import * as m from '$paraglide/messages'; -import { fail, type Actions } from '@sveltejs/kit'; -import { setFlash } from 'sveltekit-flash-message/server'; -import { setError, superValidate } from 'sveltekit-superforms'; +import { type Actions } from '@sveltejs/kit'; +import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import { z } from 'zod'; import type { PageServerLoad } from './$types'; -import { defaultDeleteFormAction, defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const schema = z.object({ id: z.string().uuid() }); From 33eeb5675bc9131f754ce217e6cc6b0e048235d8 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:10:47 +0200 Subject: [PATCH 16/23] Use handleErrorResponse in SSO settings --- .../(app)/(internal)/settings/+page.server.ts | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/settings/+page.server.ts b/frontend/src/routes/(app)/(internal)/settings/+page.server.ts index 325f13654..e3f2759d4 100644 --- a/frontend/src/routes/(app)/(internal)/settings/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/settings/+page.server.ts @@ -1,12 +1,13 @@ +import { handleErrorResponse } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; import { getModelInfo } from '$lib/utils/crud'; import { SSOSettingsSchema } from '$lib/utils/schemas'; +import * as m from '$paraglide/messages'; import { fail, type Actions } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; -import { setError, superValidate } from 'sveltekit-superforms'; +import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import type { PageServerLoad } from './$types'; -import * as m from '$paraglide/messages'; export const load: PageServerLoad = async ({ fetch }) => { const settings = await fetch(`${BASE_API_URL}/settings/sso/object/`).then((res) => res.json()); @@ -56,24 +57,12 @@ export const actions: Actions = { body: JSON.stringify(form.data) }; - const res = await event.fetch(endpoint, requestInitOptions); + const response = await event.fetch(endpoint, requestInitOptions); + + if (!response.ok) return handleErrorResponse({ event, response, form }); - if (!res.ok) { - const response: Record = await res.json(); - console.error(response); - if (response.warning) { - setFlash({ type: 'warning', message: response.warning }, event); - return { form }; - } - if (response.error) { - setFlash({ type: 'error', message: response.error }, event); - return { form }; - } - Object.entries(response).forEach(([key, value]) => { - setError(form, key, value); - }); - return fail(400, { form }); - } setFlash({ type: 'success', message: m.ssoSettingsupdated() }, event); + + return { form }; } }; From 4ca092ceb74c1a64c46f1332ea9dd1e9f709f80f Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:13:49 +0200 Subject: [PATCH 17/23] Use handleErrorResponse for attachment deletion form action --- .../evidences/[id=uuid]/+page.server.ts | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/frontend/src/routes/(app)/(third-party)/evidences/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/evidences/[id=uuid]/+page.server.ts index 6ec60f0e9..6f0aeaad3 100644 --- a/frontend/src/routes/(app)/(third-party)/evidences/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/evidences/[id=uuid]/+page.server.ts @@ -1,14 +1,15 @@ +import { handleErrorResponse } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; -import { fail, redirect, type Actions } from '@sveltejs/kit'; -import { z } from 'zod'; -import { setError, superValidate } from 'sveltekit-superforms'; -import { setFlash } from 'sveltekit-flash-message/server'; -import type { PageServerLoad } from './$types'; -import type { urlModel } from '$lib/utils/types'; import { listViewFields } from '$lib/utils/table'; -import { tableSourceMapper, type TableSource } from '@skeletonlabs/skeleton'; +import type { urlModel } from '$lib/utils/types'; import * as m from '$paraglide/messages'; +import { tableSourceMapper, type TableSource } from '@skeletonlabs/skeleton'; +import { fail, redirect, type Actions } from '@sveltejs/kit'; +import { setFlash } from 'sveltekit-flash-message/server'; +import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; +import { z } from 'zod'; +import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ fetch, params }) => { const URLModel = 'evidences'; @@ -49,29 +50,23 @@ export const actions: Actions = { deleteAttachment: async (event) => { const formData = await event.request.formData(); const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const deleteAttachmentForm = await superValidate(formData, zod(schema)); + const form = await superValidate(formData, zod(schema)); - const urlmodel = deleteAttachmentForm.data.urlmodel; - const id = deleteAttachmentForm.data.id; + const urlmodel = form.data.urlmodel; + const id = form.data.id; const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/delete_attachment/`; - if (!deleteAttachmentForm.valid) { - return fail(400, { form: deleteAttachmentForm }); + if (!form.valid) { + return fail(400, { form: form }); } const requestInitOptions: RequestInit = { method: 'POST' }; - const res = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - if (response.non_field_errors) { - setError(deleteAttachmentForm, 'non_field_errors', response.non_field_errors); - } - setFlash({ type: 'error', message: m.anErrorOccurred() }, event); - return fail(400, { form: deleteAttachmentForm }); - } + + const response = await event.fetch(endpoint, requestInitOptions); + if (!response.ok) return handleErrorResponse({ event, response, form }); setFlash({ type: 'success', message: m.attachmentDeleted() }, event); - throw redirect(302, `/${urlmodel}/${id}`); + return redirect(302, `/${urlmodel}/${id}`); } }; From 188c9c7c995dc9982462a773d3440329bc6ce5b9 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:17:51 +0200 Subject: [PATCH 18/23] Use handleErrorResponse for risk assessment duplication form action --- .../[id=uuid]/+page.server.ts | 72 +++++++++---------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts index d91a4887a..36687b5e8 100644 --- a/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/risk-assessments/[id=uuid]/+page.server.ts @@ -4,7 +4,11 @@ import { safeTranslate } from '$lib/utils/i18n'; import * as m from '$paraglide/messages'; -import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; +import { + handleErrorResponse, + nestedDeleteFormAction, + nestedWriteFormAction +} from '$lib/utils/actions'; import { modelSchema } from '$lib/utils/schemas'; import { fail, type Actions } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; @@ -18,50 +22,42 @@ export const actions: Actions = { delete: async (event) => { return nestedDeleteFormAction({ event }); }, - duplicate: async ({ request, fetch, params, cookies }) => { - const formData = await request.formData(); + duplicate: async (event) => { + const formData = await event.request.formData(); + + if (!formData) return; const schema = modelSchema(formData.get('urlmodel') as string); const urlModel = 'risk-assessments'; - const createForm = await superValidate(formData, zod(schema)); + const form = await superValidate(formData, zod(schema)); - const endpoint = `${BASE_API_URL}/${urlModel}/${params.id}/duplicate/`; + const endpoint = `${BASE_API_URL}/${urlModel}/${event.params.id}/duplicate/`; - if (!createForm.valid) { - console.log(createForm.errors); - return fail(400, { form: createForm }); + if (!form.valid) { + console.log(form.errors); + return fail(400, { form: form }); } - if (formData) { - const requestInitOptions: RequestInit = { - method: 'POST', - body: JSON.stringify(createForm.data) - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response: Record = await res.json(); - console.log(response); - if (response.non_field_errors) { - setError(createForm, 'non_field_errors', response.non_field_errors); - } - Object.entries(response).forEach(([key, value]) => { - setError(createForm, key, value); - }); - return fail(400, { form: createForm }); - } - const modelVerboseName: string = urlParamModelVerboseName(urlModel); - // TODO: reference newly created object - setFlash( - { - type: 'success', - message: m.successfullyDuplicateObject({ - object: safeTranslate(modelVerboseName).toLowerCase() - }) - }, - cookies - ); - } - return { createForm }; + const requestInitOptions: RequestInit = { + method: 'POST', + body: JSON.stringify(form.data) + }; + const response = await event.fetch(endpoint, requestInitOptions); + + if (!response.ok) return handleErrorResponse({ event, response, form }); + + const modelVerboseName: string = urlParamModelVerboseName(urlModel); + setFlash( + { + type: 'success', + message: m.successfullyDuplicateObject({ + object: safeTranslate(modelVerboseName).toLowerCase() + }) + }, + event + ); + + return { form }; } }; From cf762cb0df37757cd9e49ce34247c12ba3d40a4e Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:19:20 +0200 Subject: [PATCH 19/23] chore: Remove dead code --- .../[id=uuid]/edit/+page.server.ts | 13 ++----------- .../[id=uuid]/+page.server.ts | 19 +++++++------------ 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts index 1924ac189..a8547eb4c 100644 --- a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts @@ -1,19 +1,18 @@ +import { nestedWriteFormAction } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; -import { localItems, toCamelCase } from '$lib/utils/locales'; +import { getSecureRedirect } from '$lib/utils/helpers'; import { modelSchema } from '$lib/utils/schemas'; import { listViewFields } from '$lib/utils/table'; import type { urlModel } from '$lib/utils/types'; import * as m from '$paraglide/messages'; import { tableSourceMapper, type TableSource } from '@skeletonlabs/skeleton'; import type { Actions } from '@sveltejs/kit'; -import { getSecureRedirect } from '$lib/utils/helpers'; import { fail, redirect } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; import { setError, superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; import type { PageServerLoad } from './$types'; -import { nestedWriteFormAction } from '$lib/utils/actions'; export const load = (async ({ fetch, params }) => { const URLModel = 'requirement-assessments'; @@ -196,14 +195,6 @@ export const load = (async ({ fetch, params }) => { continue; } evidenceForeignKeys[keyField.field] = []; - // const queryParams = keyField.urlParams ? `?${keyField.urlParams}` : ''; - // const url = `${BASE_API_URL}/${keyField.urlModel}/${queryParams}`; - // const response = await fetch(url); - // if (response.ok) { - // evidenceForeignKeys[keyField.field] = await response.json().then((data) => data.results); - // } else { - // console.error(`Failed to fetch data for ${keyField.field}: ${response.statusText}`); - // } } } diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts index fa7e305f9..5e0725601 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/+page.server.ts @@ -1,13 +1,11 @@ +import { nestedWriteFormAction } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; -import { ComplianceAssessmentSchema, modelSchema } from '$lib/utils/schemas'; -import { message, setError, superValidate } from 'sveltekit-superforms'; -import type { PageServerLoad } from './$types'; +import { getModelInfo } from '$lib/utils/crud'; +import { ComplianceAssessmentSchema } from '$lib/utils/schemas'; +import { type Actions } from '@sveltejs/kit'; +import { superValidate } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; -import { fail, type Actions } from '@sveltejs/kit'; -import * as m from '$paraglide/messages'; -import { localItems, toCamelCase } from '$lib/utils/locales'; -import { nestedWriteFormAction } from '$lib/utils/actions'; +import type { PageServerLoad } from './$types'; export const load = (async ({ fetch, params }) => { const URLModel = 'compliance-assessments'; @@ -57,10 +55,7 @@ export const load = (async ({ fetch, params }) => { } const mappingSetsEndpoint = `${BASE_API_URL}/requirement-mapping-sets/?reference_framework=${compliance_assessment.framework.id}`; - const mappingSets: Record[] = await fetch(mappingSetsEndpoint) - .then((res) => res.json()) - .then((data) => data.results); - const mappingSetIds = mappingSets.map((mappingSet) => mappingSet.id); + auditModel.foreignKeys = foreignKeys; const selectOptions: Record = {}; From b31b3ce541106703b80f945f5ba2220bed8eac06 Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:22:12 +0200 Subject: [PATCH 20/23] Use handleErrorResponse for requirement assessment form actions --- .../[id=uuid]/edit/+page.server.ts | 41 ++++--------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts index a8547eb4c..b8aef03fc 100644 --- a/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts @@ -1,4 +1,4 @@ -import { nestedWriteFormAction } from '$lib/utils/actions'; +import { handleErrorResponse, nestedWriteFormAction } from '$lib/utils/actions'; import { BASE_API_URL } from '$lib/utils/constants'; import { getModelInfo, urlParamModelVerboseName } from '$lib/utils/crud'; import { getSecureRedirect } from '$lib/utils/helpers'; @@ -233,20 +233,11 @@ export const actions: Actions = { body: JSON.stringify(form.data) }; - const res = await event.fetch(endpoint, requestInitOptions); + const response = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.error('server response:', response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - if (response.score) { - setError(form, 'score', response.score); - } - return fail(400, { form: form }); - } - const object = await res.json(); + if (!response.ok) return handleErrorResponse({ event, response, form }); + + const object = await response.json(); const model: string = urlParamModelVerboseName(URLModel); setFlash({ type: 'success', message: m.successfullySavedObject({ object: model }) }, event); redirect( @@ -271,18 +262,11 @@ export const actions: Actions = { body: JSON.stringify(form.data) }; - const res = await event.fetch(endpoint, requestInitOptions); + const response = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.error('server response:', response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } + if (!response.ok) return handleErrorResponse({ event, response, form }); - const measure = await res.json(); + const measure = await response.json(); const requirementAssessmentEndpoint = `${BASE_API_URL}/requirement-assessments/${event.params.id}/`; const requirementAssessment = await event @@ -297,14 +281,7 @@ export const actions: Actions = { }; const patchRes = await event.fetch(requirementAssessmentEndpoint, patchRequestInitOptions); - if (!patchRes.ok) { - const response = await patchRes.json(); - console.error('server response:', response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } + if (!patchRes.ok) return handleErrorResponse({ event, response: patchRes, form }); const model: string = urlParamModelVerboseName(URLModel); setFlash( From b25f552f11e3e24b139ab644fb438fe1db769f6a Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:23:28 +0200 Subject: [PATCH 21/23] Remove risk acceptance related form actions from third party routes --- .../[id=uuid]/+page.server.ts | 113 +----------------- 1 file changed, 1 insertion(+), 112 deletions(-) diff --git a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts index 6592300a6..db4dabd45 100644 --- a/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/[model=thirdparty_urlmodels]/[id=uuid]/+page.server.ts @@ -1,14 +1,5 @@ -import { BASE_API_URL } from '$lib/utils/constants'; -import { urlParamModelVerboseName } from '$lib/utils/crud'; - -import { localItems, toCamelCase } from '$lib/utils/locales'; -import * as m from '$paraglide/messages'; - import { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; -import { fail, type Actions } from '@sveltejs/kit'; -import { message, setError, superValidate } from 'sveltekit-superforms'; -import { zod } from 'sveltekit-superforms/adapters'; -import { z } from 'zod'; +import { type Actions } from '@sveltejs/kit'; export const actions: Actions = { create: async (event) => { @@ -16,107 +7,5 @@ export const actions: Actions = { }, delete: async (event) => { return nestedDeleteFormAction({ event }); - }, - reject: async ({ request, fetch, params }) => { - const formData = await request.formData(); - const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const rejectForm = await superValidate(formData, zod(schema)); - - const urlmodel = rejectForm.data.urlmodel; - const id = rejectForm.data.id; - const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/reject/`; - - if (!rejectForm.valid) { - return fail(400, { form: rejectForm }); - } - - const requestInitOptions: RequestInit = { - method: 'POST' - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - if (response.non_field_errors) { - setError(rejectForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: rejectForm }); - } - const model: string = urlParamModelVerboseName(params.model!); - // TODO: reference object by name instead of id - return message( - rejectForm, - m.successfullyRejectedObject({ - object: localItems()[toCamelCase(model.toLowerCase())].toLowerCase(), - id: id - }) - ); - }, - accept: async ({ request, fetch, params }) => { - const formData = await request.formData(); - const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const acceptForm = await superValidate(formData, zod(schema)); - - const urlmodel = acceptForm.data.urlmodel; - const id = acceptForm.data.id; - const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/accept/`; - - if (!acceptForm.valid) { - return fail(400, { form: acceptForm }); - } - - const requestInitOptions: RequestInit = { - method: 'POST' - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - if (response.non_field_errors) { - setError(acceptForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: acceptForm }); - } - const model: string = urlParamModelVerboseName(params.model!); - // TODO: reference object by name instead of id - return message( - acceptForm, - m.successfullyValidatedObject({ - object: localItems()[toCamelCase(model.toLowerCase())].toLowerCase(), - id: id - }) - ); - }, - revoke: async ({ request, fetch, params }) => { - const formData = await request.formData(); - const schema = z.object({ urlmodel: z.string(), id: z.string().uuid() }); - const revokeForm = await superValidate(formData, zod(schema)); - - const urlmodel = revokeForm.data.urlmodel; - const id = revokeForm.data.id; - const endpoint = `${BASE_API_URL}/${urlmodel}/${id}/revoke/`; - - if (!revokeForm.valid) { - return fail(400, { form: revokeForm }); - } - - const requestInitOptions: RequestInit = { - method: 'POST' - }; - const res = await fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - if (response.non_field_errors) { - setError(revokeForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: revokeForm }); - } - const model: string = urlParamModelVerboseName(params.model!); - // TODO: reference object by name instead of id - return message( - revokeForm, - m.successfullyRevokedObject({ - object: localItems()[toCamelCase(model.toLowerCase())].toLowerCase(), - id: id - }) - ); } }; From 2c6ce9c5f72c5c86e854b73a2f55fbead95d123d Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:28:50 +0200 Subject: [PATCH 22/23] Remove deleteEvidence form action from table mode --- .../[id=uuid]/table-mode/+page.server.ts | 43 ------------------- .../[id=uuid]/table-mode/+page.svelte | 4 +- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts index 8d6d692b3..bbf7abd2f 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.server.ts @@ -92,48 +92,5 @@ export const actions: Actions = { }, createEvidence: async (event) => { return nestedWriteFormAction({ event, action: 'create' }); - }, - deleteEvidence: async (event) => { - const formData = await event.request.formData(); - const schema = z.object({ id: z.string().uuid() }); - const deleteForm = await superValidate(formData, zod(schema)); - - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/evidences/${id}/`; - - if (!deleteForm.valid) { - console.log(deleteForm.errors); - return fail(400, { form: deleteForm }); - } - - if (formData.has('delete')) { - const requestInitOptions: RequestInit = { - method: 'DELETE' - }; - const res = await event.fetch(endpoint, requestInitOptions); - if (!res.ok) { - const response = await res.json(); - console.log(response); - if (response.error) { - setFlash({ type: 'error', message: safeTranslate(response.error) }, event); - return fail(403, { form: deleteForm }); - } - if (response.non_field_errors) { - setError(deleteForm, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: deleteForm }); - } - const model: string = urlParamModelVerboseName(event.params.model!); - setFlash( - { - type: 'success', - message: m.successfullyDeletedObject({ - object: safeTranslate(model).toLowerCase() - }) - }, - event - ); - } - return { deleteForm }; } }; diff --git a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte index 63d2aef92..1405c695b 100644 --- a/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/compliance-assessments/[id=uuid]/table-mode/+page.svelte @@ -128,7 +128,7 @@ ref: DeleteConfirmModal, props: { _form: data.deleteForm, - formAction: `${actionPath}?/deleteEvidence`, + formAction: `/evidences?/delete`, id: id, invalidateAll: invalidateAll, debug: false, @@ -140,7 +140,7 @@ component: modalComponent, // Data title: m.deleteModalTitle(), - body: `${m.deleteModalMessage()}: ${name}?` + body: `${m.deleteModalMessage({ name })}` }; modalStore.trigger(modal); data.requirements.forEach((requirementAssessment) => { From b86eed9f2e95ebb8a7f5ff5395ff4459ab78adfa Mon Sep 17 00:00:00 2001 From: Nassim Tabchiche Date: Tue, 24 Sep 2024 17:33:58 +0200 Subject: [PATCH 23/23] Some tidying --- frontend/src/lib/utils/actions.ts | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts index 7c782a05e..9bbf8dacd 100644 --- a/frontend/src/lib/utils/actions.ts +++ b/frontend/src/lib/utils/actions.ts @@ -5,12 +5,12 @@ import * as m from '$paraglide/messages'; import { safeTranslate } from '$lib/utils/i18n'; import { modelSchema } from '$lib/utils/schemas'; -import { type RequestEvent, fail, redirect } from '@sveltejs/kit'; +import { fail, redirect, type RequestEvent } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; import { setError, superValidate, type SuperValidated } from 'sveltekit-superforms'; import { zod } from 'sveltekit-superforms/adapters'; -import { getSecureRedirect } from './helpers'; import { z } from 'zod'; +import { getSecureRedirect } from './helpers'; type FormAction = 'create' | 'edit'; @@ -108,9 +108,9 @@ export async function defaultWriteFormAction({ const endpoint = getEndpoint({ action, urlModel, event }); const model = getModelInfo(urlModel!); - const fileFields: Record = Object.fromEntries( + const fileFields = Object.fromEntries( Object.entries(form.data).filter(([key]) => model.fileFields?.includes(key) ?? false) - ); + ) as Record; Object.keys(fileFields).forEach((key) => { form.data[key] = undefined; @@ -125,13 +125,13 @@ export async function defaultWriteFormAction({ if (!res.ok) return await handleErrorResponse({ event, response: res, form }); - const createdObject = await res.json(); + const writtenObject = await res.json(); if (fileFields) { for (const [, file] of Object.entries(fileFields)) { if (!file) continue; if (file.size <= 0) continue; - const fileUploadEndpoint = `${BASE_API_URL}/${urlModel}/${createdObject.id}/upload/`; + const fileUploadEndpoint = `${BASE_API_URL}/${urlModel}/${writtenObject.id}/upload/`; const fileUploadRequestInitOptions: RequestInit = { headers: { 'Content-Disposition': `attachment; filename=${encodeURIComponent(file.name)}` @@ -140,14 +140,7 @@ export async function defaultWriteFormAction({ body: file }; const fileUploadRes = await event.fetch(fileUploadEndpoint, fileUploadRequestInitOptions); - if (!fileUploadRes.ok) { - const response = await fileUploadRes.json(); - console.error(response); - if (response.non_field_errors) { - setError(form, 'non_field_errors', response.non_field_errors); - } - return fail(400, { form: form }); - } + if (!fileUploadRes.ok) return handleErrorResponse({ event, response: fileUploadRes, form }); } } @@ -162,7 +155,7 @@ export async function defaultWriteFormAction({ const next = getSecureRedirect(event.url.searchParams.get('next')); if (next && doRedirect) redirect(302, next); - return { createForm: form }; + return { form }; } export async function nestedWriteFormAction({