diff --git a/frontend/src/lib/utils/actions.ts b/frontend/src/lib/utils/actions.ts new file mode 100644 index 000000000..9bbf8dacd --- /dev/null +++ b/frontend/src/lib/utils/actions.ts @@ -0,0 +1,230 @@ +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 { 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 { z } from 'zod'; +import { getSecureRedirect } from './helpers'; + +type FormAction = 'create' | 'edit'; + +function getHTTPMethod({ + action, + fileFields +}: { + 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() + }); + } +} + +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 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, + action, + doRedirect = true +}: { + event: RequestEvent; + urlModel: string; + action: FormAction; + doRedirect?: boolean; +}) { + 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 = getEndpoint({ action, urlModel, event }); + const model = getModelInfo(urlModel!); + + 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; + }); + + const requestInitOptions: RequestInit = { + method: getHTTPMethod({ action, fileFields }), + body: JSON.stringify(form.data) + }; + + const res = await event.fetch(endpoint, requestInitOptions); + + if (!res.ok) return await handleErrorResponse({ event, response: res, form }); + + 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}/${writtenObject.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) return handleErrorResponse({ event, response: fileUploadRes, form }); + } + } + + setFlash( + { + type: 'success', + message: getSuccessMessage({ urlModel, action }) as string + }, + event + ); + + const next = getSecureRedirect(event.url.searchParams.get('next')); + if (next && doRedirect) redirect(302, next); + + return { 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 }); +} + +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 }); +} 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..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,18 +1,14 @@ +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'; @@ -67,150 +63,9 @@ 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(); - 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 23d21aba6..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 @@ -4,13 +4,11 @@ import { getModelInfo, 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 { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; import { loadDetail } from '$lib/utils/load'; import type { PageServerLoad } from './$types'; @@ -21,146 +19,10 @@ export const load: PageServerLoad = async (event) => { 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 || 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 }; + 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)/[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' }); } }; 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)/requirement-assessments/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(internal)/requirement-assessments/[id=uuid]/edit/+page.server.ts index b4243d07c..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,13 +1,13 @@ +import { handleErrorResponse, 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'; @@ -195,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}`); - // } } } @@ -241,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( @@ -279,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 @@ -305,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( @@ -325,92 +294,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..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 @@ -1,138 +1,63 @@ -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 { + handleErrorResponse, + 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 { setError, superValidate } from 'sveltekit-superforms'; +import { zod } from 'sveltekit-superforms/adapters'; export const actions: Actions = { - create: async ({ request, fetch }) => { - const formData = await request.formData(); + create: async (event) => { + return nestedWriteFormAction({ event, action: 'create' }); + }, + delete: async (event) => { + return nestedDeleteFormAction({ event }); + }, + duplicate: async (event) => { + const formData = await event.request.formData(); + + if (!formData) return; const schema = modelSchema(formData.get('urlmodel') as string); - const urlModel = formData.get('urlmodel'); + const urlModel = 'risk-assessments'; - const createForm = await superValidate(formData, zod(schema)); + const form = await superValidate(formData, zod(schema)); - const endpoint = `${BASE_API_URL}/${urlModel}/`; + 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 = 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 }; - }, - 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 requestInitOptions: RequestInit = { + method: 'POST', + body: JSON.stringify(form.data) + }; + const response = await event.fetch(endpoint, requestInitOptions); - const id = deleteForm.data.id; - const endpoint = `${BASE_API_URL}/risk-scenarios/${id}/`; + if (!response.ok) return handleErrorResponse({ event, response, form }); - 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() + const modelVerboseName: string = urlParamModelVerboseName(urlModel); + setFlash( + { + type: 'success', + message: m.successfullyDuplicateObject({ + object: safeTranslate(modelVerboseName).toLowerCase() }) - ); - } - return { deleteForm }; - }, - duplicate: async ({ request, fetch, params, cookies }) => { - const formData = await request.formData(); - - const schema = modelSchema(formData.get('urlmodel') as string); - const urlModel = 'risk-assessments'; + }, + event + ); - const createForm = await superValidate(formData, zod(schema)); - - const endpoint = `${BASE_API_URL}/${urlModel}/${params.id}/duplicate/`; - - 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: 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 }; + return { form }; } }; 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..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 @@ -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: 'risk-scenarios', action: 'edit' }); }, createAppliedControl: async (event) => { const URLModel = 'applied-controls'; 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 }; } }; 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..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,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 { defaultDeleteFormAction, defaultWriteFormAction } from '$lib/utils/actions'; export const load: PageServerLoad = async ({ params, fetch }) => { const schema = z.object({ id: z.string().uuid() }); @@ -67,150 +68,13 @@ 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(); - 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 aa69cf586..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,260 +1,11 @@ -import { BASE_API_URL } from '$lib/utils/constants'; -import { getModelInfo, 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 { nestedDeleteFormAction, nestedWriteFormAction } from '$lib/utils/actions'; +import { type Actions } from '@sveltejs/kit'; 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 }; - }, - 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 }; - }, - 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 - }) - ); + return nestedWriteFormAction({ event, action: 'create' }); }, - 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 - }) - ); + delete: async (event) => { + return nestedDeleteFormAction({ event }); } }; 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' }); } }; 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..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,12 +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 type { PageServerLoad } from './$types'; export const load = (async ({ fetch, params }) => { const URLModel = 'compliance-assessments'; @@ -56,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 = {}; @@ -96,44 +92,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..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 @@ -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,137 +91,6 @@ 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 }; - }, - 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 }; + return nestedWriteFormAction({ event, action: 'create' }); } }; 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) => { 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}`); } };