-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #856 from intuitem/refactor/code-duplication
refactor/code duplication
- Loading branch information
Showing
16 changed files
with
350 additions
and
1,431 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string, File>; | ||
}) { | ||
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<string, string> = 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<string, File>; | ||
|
||
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 }); | ||
} |
Oops, something went wrong.