From 3d798d9c6f72c6334376271b8f274221f2c70638 Mon Sep 17 00:00:00 2001 From: Andre Abelmann <3215327+aabelmann@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:08:26 +0100 Subject: [PATCH] Add simplified errors --- nuxt/composables/useHumanReableMessage.ts | 18 +++++ nuxt/pages/create/index.vue | 54 ++++++++------ nuxt/server/api/flixes/index.post.ts | 19 +++-- nuxt/stores/flix.ts | 5 +- strapi/src/api/flix/controllers/flix.js | 53 +++++++++---- strapi/src/api/flix/services/flix.js | 90 ++++++++++++++++++++++- 6 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 nuxt/composables/useHumanReableMessage.ts diff --git a/nuxt/composables/useHumanReableMessage.ts b/nuxt/composables/useHumanReableMessage.ts new file mode 100644 index 0000000..06345e2 --- /dev/null +++ b/nuxt/composables/useHumanReableMessage.ts @@ -0,0 +1,18 @@ +export default (code: string) => { + switch (code) { + case 'INVALID_FIELDS': + return 'Gelieve alle velden in te vullen.'; + case 'INVALID_CATEGORY_QUERY': + return 'Er zit een fout in de Category Query'; + case 'INVALID_CATEGORY_STRUCTURE': + return 'De Category Query geeft niet de juiste velden terug'; + case 'INVALID_CATEGORY_ID': + return 'De Category Query gaf geen resultaten terug'; + case 'INVALID_ITEMS_QUERY': + return 'Er zit een fout in de Items Query'; + case 'INVALID_ITEMS_STRUCTURE': + return 'De Items Query geeft niet de juiste velden terug'; + default: + return 'Er is een onbekende fout opgetreden.'; + } +}; diff --git a/nuxt/pages/create/index.vue b/nuxt/pages/create/index.vue index 7dbfaf3..a5890cf 100644 --- a/nuxt/pages/create/index.vue +++ b/nuxt/pages/create/index.vue @@ -73,29 +73,39 @@ await flixStore.createDraft(query.token); * Methods */ const nextStep = async () => { - const { data } = unref(currentFlix); - - if (!data.endpointUrl || !data.categoryQuery || !data.itemsQuery) { - showErrors.value = true; - errorMessages.value = 'Gelieve alle velden in te vullen.'; - return; - } // Save the current flix - const token = await flixStore.saveDraft(query.token); - - // If there was an error, show a generic message. - // Note: Don't show the actual error to the user, since it's most likely an issue for us, unlike validation errors - if (!token) { - showErrors.value = true; - errorMessages.value = 'Er is iets fout gegaan bij het opslaan, controlleer alle velden en probeer het opnieuw.'; - } else { - // Goto the next step - navigateTo({ - path: `/create/preview`, - query: { - token, - }, - }); + const response = await flixStore.saveDraft(query.token); + + // Only do things if we get a response + if (response) { + // Error or not, we update the path if we don't have a token yet. + if (!query.token && response.hash) { + await navigateTo( + { + path: `/create`, + query: { + token: response.hash, + }, + }, + { + replace: true, + }, + ); + } + + // Check if the response has a message, if so update showErrors and errorMessages + if (response.code) { + showErrors.value = true; + errorMessages.value = useHumanReableMessage(response.code); + } else { + // Goto the next step + await navigateTo({ + path: `/create/preview`, + query: { + token: response.hash, + }, + }); + } } }; diff --git a/nuxt/server/api/flixes/index.post.ts b/nuxt/server/api/flixes/index.post.ts index 3bc3a97..0342570 100644 --- a/nuxt/server/api/flixes/index.post.ts +++ b/nuxt/server/api/flixes/index.post.ts @@ -8,17 +8,20 @@ export default defineEventHandler(async event => { const headers = generateHeaders(event); const url = `${backendUrl}/flixes${body.id ? `/${body.id}` : ''}`; const method = body.id ? 'PUT' : 'POST'; - const { data } = await $fetch>>(url, { + const response = await $fetch>(url, { method, headers, body: JSON.stringify({ data: body }), }); - return { - flix: { - ...body, - id: data.id, - }, - hash: data.attributes.hash, - }; + if (response?.id) { + return { + flix: { + ...body, + id: response.id, + }, + hash: response.hash, + }; + } + return response; }); diff --git a/nuxt/stores/flix.ts b/nuxt/stores/flix.ts index 1f78f83..b0b405c 100644 --- a/nuxt/stores/flix.ts +++ b/nuxt/stores/flix.ts @@ -99,7 +99,7 @@ export const useFlixStore = defineStore('flix', () => { } }; - const saveDraft = async (publish?: true): Promise => { + const saveDraft = async (publish?: true): Promise | undefined> => { // No currentFlix to save, just return if (!currentFlix.value) { return; @@ -115,9 +115,8 @@ export const useFlixStore = defineStore('flix', () => { if (response?.flix) { currentFlix.value = response.flix; currentToken.value = response.hash; - return response.hash; } - return; + return response; }; return { diff --git a/strapi/src/api/flix/controllers/flix.js b/strapi/src/api/flix/controllers/flix.js index 30d128e..25f0ada 100644 --- a/strapi/src/api/flix/controllers/flix.js +++ b/strapi/src/api/flix/controllers/flix.js @@ -16,29 +16,50 @@ module.exports = createCoreController('api::flix.flix', ({ strapi }) => ({ const [entry] = await findFlix(filters); return entry; }, + async create(ctx) { + const { validateEndpoints } = strapi.service('api::flix.flix'); + + // Fetch the data and if from the context + const data = ctx.request.body.data; + + // Before create anything, atleast ensure the data is filled in. + if (!data.data.endpointUrl || !data.data.itemsQuery || !data.data.categoryQuery) { + return { code: 'INVALID_FIELDS' }; + } + + // Call Strapi's internal entity service to avoid invoking overridden methods + const updatedEntity = await strapi.entityService.create('api::flix.flix', { + data + }); + + // Sanitize the output to ensure it conforms to Strapi's response format + const sanitizedEntity = await this.sanitizeOutput(updatedEntity, ctx); + + // Validate the endpoints + return validateEndpoints(sanitizedEntity); + }, async update(ctx) { if (!ctx.request.header['x-token']) { return ctx.badRequest('No token provided'); } + const { generateUniqueUri, validateEndpoints } = strapi.service('api::flix.flix'); + + // Update the uri incase there are more flixes with the same name + ctx.request.body.data.uri = await generateUniqueUri(ctx.request.body.data); - // Make sure the uri is unique by checking the count of flixes with same name - let { branding: { name }, uri, id } = ctx.request.body.data; - - const count = await strapi.entityService.count('api::flix.flix', { - filters: { - branding: { - name - }, - id: { - $ne: id - } - } + // Fetch the data and if from the context + const { id } = ctx.params; + const data = ctx.request.body.data; + + // Call Strapi's internal entity service to avoid invoking overridden methods + const updatedEntity = await strapi.entityService.update('api::flix.flix', id, { + data }); - if (count) { - ctx.request.body.data.uri = `${uri}-${count}`; - } + // Sanitize the output to ensure it conforms to Strapi's response format + const sanitizedEntity = await this.sanitizeOutput(updatedEntity, ctx); - return super.update(ctx); + // Validate the endpoints + return validateEndpoints(sanitizedEntity); } })) diff --git a/strapi/src/api/flix/services/flix.js b/strapi/src/api/flix/services/flix.js index 50574fc..19e1733 100644 --- a/strapi/src/api/flix/services/flix.js +++ b/strapi/src/api/flix/services/flix.js @@ -1,4 +1,90 @@ 'use strict'; -const { createCoreService } = require('@strapi/strapi').factories; -module.exports = createCoreService('api::flix.flix'); +const CATEGORY_MIN_PROPS = ['numberOfHeritageObjects', 'id', 'name']; +const ITEM_MIN_PROPS = ['heritageObject', 'name', 'description']; + +const validateStructure = (keys, requiredProps) => { + return requiredProps.every(prop => keys.includes(prop)); +}; + +const generateResponse = (hash, code) => { + return { + hash, + code + }; +}; + +module.exports = () => ({ + generateUniqueUri: async ({ branding: { name }, uri, id }) => { + if (!name || !uri || !id) { + return ''; + } + + // Count the number of flixes with the same branding name + const count = await strapi.entityService.count('api::flix.flix', { + filters: { + branding: { + name + }, + id: { + $ne: id + } + } + }); + return count ? `${uri}-${count}` : uri; + }, + validateEndpoints: async (sanitizedEntry) => { + // Fetch the data based on the id in the sanitized entry + const originalEntry = await strapi.entityService.findOne('api::flix.flix', sanitizedEntry.id, { + populate: ['data'] + }); + if (!originalEntry.data) { + throw new Error('No data found'); + } + + const { endpointUrl, itemsQuery, categoryQuery } = originalEntry.data; + if (!endpointUrl || !itemsQuery || !categoryQuery) { + return generateResponse(sanitizedEntry.hash, 'INVALID_FIELDS'); + } + + // Validate the category query + const { getCategories } = strapi.service('api::categories.categories'); + const { head, results, response } = await getCategories(categoryQuery, endpointUrl, 1); + + // If the response status is not 200, return the status and message + if (response?.status && response.status !== 200) { + return generateResponse(sanitizedEntry.hash, 'INVALID_CATEGORY_QUERY'); + } + + // Validate the category structure + const isValidCategory = validateStructure(head.vars, CATEGORY_MIN_PROPS); + if (!isValidCategory) { + return generateResponse(sanitizedEntry.hash, 'INVALID_CATEGORY_STRUCTURE'); + } + + // Valid category, so take the first id and test the items query + const id = results.bindings?.[0].id.value; + if (!id) { + return generateResponse(sanitizedEntry.hash, 'INVALID_CATEGORY_ID'); + } + + // Validate the items query + const { getItemsByCategory } = strapi.service('api::items.items'); + const { head: itemsHead, response: itemsResponse } = await getItemsByCategory(itemsQuery, endpointUrl, id, 1, 0); + + // If the response status is not 200, return the status and message + if (itemsResponse?.status && itemsResponse.status !== 200) { + return generateResponse(sanitizedEntry.hash, 'INVALID_ITEMS_QUERY'); + } + + // Validate the items structure + const isValidItems = validateStructure(itemsHead.vars, ITEM_MIN_PROPS); + console.warn(isValidItems, itemsHead.vars, ITEM_MIN_PROPS); + if (!isValidItems) { + return generateResponse(sanitizedEntry.hash, 'INVALID_ITEMS_STRUCTURE'); + } + + // Everything is fine, just return the sanitized entry + return sanitizedEntry; + } +});