Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: GH-20 #33

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions nuxt/composables/useHumanReableMessage.ts
Original file line number Diff line number Diff line change
@@ -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.';
}
};
54 changes: 32 additions & 22 deletions nuxt/pages/create/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});
}
}
};
</script>
Expand Down
19 changes: 11 additions & 8 deletions nuxt/server/api/flixes/index.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<StrapiApiResponse<StrapiEntity<Flix>>>(url, {
const response = await $fetch<Record<string, any>>(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;
});
5 changes: 2 additions & 3 deletions nuxt/stores/flix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const useFlixStore = defineStore('flix', () => {
}
};

const saveDraft = async (publish?: true): Promise<string | void> => {
const saveDraft = async (publish?: true): Promise<Record<string, any> | undefined> => {
// No currentFlix to save, just return
if (!currentFlix.value) {
return;
Expand All @@ -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 {
Expand Down
53 changes: 37 additions & 16 deletions strapi/src/api/flix/controllers/flix.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}))
90 changes: 88 additions & 2 deletions strapi/src/api/flix/services/flix.js
Original file line number Diff line number Diff line change
@@ -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;
}
});