This is in the onReady function of the item page
import wixSeo from 'wix-seo';
$w.onReady( function () {
$w('#dynamicDataset').onReady(() => {
thisCourse = $w('#dynamicDataset').getCurrentItem();
// console.log("this course : " , JSON.stringify(thisCourse));
courseSchema(thisCourse._id)
.then(schema => {
if (!schema) throw new Error("schema is undefined or null")
console.log("setting schema")
console.log(schema);
wixSeo.setStructuredData(schema)
.then(() => {
console.log("structured data set");
})
.catch(e => {
console.log("failed setting structured data : ", e.message);
});
})
.catch(err => {
console.log("Error : ", err, err.message)
});
})
}
Backend
import wixData from 'wix-data';
const baseUrl = "https://www.scottishrockandwater.com/";
const DBOptions = { suppressAuth: true };
const validFromMonth = 8;
let courseShema = {
"@context": "https://schema.org",
"@type": "Event",
"name": "{name}",
"description": "{description}",
"location": {
"@type": "Place",
"name": "{name}",
"address": {
"@type": "PostalAddress",
"streetAddress": "{streetAddress}",
"addressLocality": "{locality}",
"postalCode": "{code}",
"addressRegion": "{region}",
"addressCountry": "{country}"
}
},
"image": [
"{image1}",
"{image2}",
"{image3}"
],
"performer": {
"@type": "PerformingGroup",
"name": "{name}"
},
"offers": {
"@type": "Offer",
"url": "{url}",
"price": "{price}",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock",
"validFrom": "{validDate}"
},
"startDate": "{startDate}",
"endDate": "{endDate}",
};
const dates = ["dateone", "dateTwo", "datethree", "dateFour", "dateFive", "dateSix", "dateOneYh", "dateTwoYh", "dateThreeYh", "dateFourYh", "dateFiveYh", "dateSixYh"];
export async function courseSchema(courseId) {
let schema = [];
try {
let course = await wixData.get("Courses", courseId);
console.log("deleting HTML...");
delete course.accommodationDetails;
delete course.details;
delete course.navigationButtons;
delete course.tripDetails;
delete course.tripHighlights;
delete course.whatDoINeedToBring;
delete course.whatElseCanWeProvide;
delete course.whatsIncluded;
delete course.whatsNotIncluded;
console.log("currect course input : " , course);
courseShema.name = course.title;
courseShema.description = course.description;
let rating = await getAvgRating(course._id, false);
if (rating.success) {
// Object.assign(schema, rating.data);
Object.assign(courseShema, rating.data);
}
let schemadetails = await wixData.query("Shema").eq("course", courseId).find(DBOptions);
console.log("schema Details : ",schemadetails);
// Object.assign(courseShema, schemadetails);
if (schemadetails.length === 0) {
// no matching items found
throw new Error("no result found");
}
console.log("currect course input 1 : " , course);
schemadetails = schemadetails.items[0];
// Todo : check visible column in the database?
// Check if it's no allow to schema
//
if (!schemadetails.visible) {
throw new Error("schema is not avaiable, visible is false")
}
courseShema.location.name = schemadetails.locationName;
courseShema.location.address.streetAddress = schemadetails.streetAddress;
courseShema.location.address.addressLocality = schemadetails.addressLocality;
courseShema.location.address.addressRegion = schemadetails.addressRegion;
courseShema.location.address.addressCountry = schemadetails.addressCountry;
courseShema.location.address.postalCode = schemadetails.postalCode;
courseShema.image[0] = removeWixImageURL(schemadetails.image1);
courseShema.image[1] = removeWixImageURL(schemadetails.image2);
courseShema.image[2] = removeWixImageURL(schemadetails.image3);
courseShema.offers.price = String(course.price);
courseShema.offers.url = baseUrl + course["link-courses-test-title"];
courseShema.performer.name = schemadetails.performerName;
console.log("currect course input 2 : " , course);
// demo
//convert this push to forloop for each date for 12 date
let totalDays = course.duration;
totalDays = Number(totalDays.split(" ")[0]);
console.log("currect course input 3 : " , course);
console.log("genr schema is created: " , courseShema)
dates.forEach(dateFieldKey => {
try{
// set Start date
let currDate = course[dateFieldKey];
console.log("currDate!!!" , currDate, typeof currDate);
let startDate = convtDate(currDate);
console.log("start date : " , startDate, typeof startDate)
// set End date
currDate.setDate(currDate.getDate() + totalDays);
let endDate = convtDate(currDate);
// set vali date 8 months before the start date
currDate.setDate(currDate.getDate() - totalDays);
currDate.setMonth(currDate.getMonth() - validFromMonth);
let validFromDate = convtDate(currDate);
console.log("valid from date : " , validFromDate)
// Update the course schema
courseShema.startDate = startDate;
courseShema.endDate = endDate;
courseShema.offers.validFrom = validFromDate;
// update the the array
schema.push(JSON.parse(JSON.stringify(courseShema)));
}
catch(e) {
console.log("Error on data" , e.message)
}
});
if(schema.length === 0) return undefined;
return schema;
}
// something went wrong
catch (error) {
console.log("Error schema is not set up : ", error, error.message);
}
}
export async function categoryCourseSchema(categoryName) {
try {
let schema = [];
let categorySchema = {
"@context": "https://schema.org/",
"@type": "Product",
"name": categoryName,
}
let rating = await getAvgRating(categoryName, true);
if (rating.success) {
Object.assign(categorySchema, rating.data);
} else {
// no rating found so stop the schema
throw new Error("rating is empty for category page : ", schema)
}
if (categorySchema) {
console.log(categorySchema);
return categorySchema;
}
// no matching items found
console.log("schema : ", schema);
throw new Error("schema is not working !!");
} catch (error) {
console.log("Error on schema : ", error.message);
}
}
async function getAvgRating(data, isCategory = true) {
let filter = wixData.filter();
if (isCategory) {
filter = filter.eq("courseCategory", data);
} else {
filter = filter.eq("courseId", data);
}
try {
// https://developers.google.com/search/docs/data-types/review-snippet
// https://developers.google.com/search/docs/data-types/event
let result = await wixData.aggregate("Reviews").filter(filter).avg("rating").count().run();
console.log(result);
if (result._items.length > 0) {
let res = result._items[0];
return {
"success": true,
"data": {
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": res.ratingAvg,
"bestRating": 5,
"ratingCount": res.count
}
}
}
}
return {
"success": false,
"No result": true
}
} catch (e) {
return {
"success": false,
"error": true,
"msg": e
}
}
}
function removeWixImageURL(url) {
if (!url) return null;
return "https://static.wixstatic.com/media/" + url.substring(url.indexOf("v1/") + 3, url.indexOf(".") + 4)
}
function convtDate(date) {
if(!date) throw new Error("date is either null or undefined");
if(typeof date === "string") return date.split("T")[0];
return date.toISOString().split("T")[0]
}
function priceConvert(str) {
if (str === null || str.length === 0) return null;
str = String(str);
str = str.split("Ā£")[1];
return Number(str);
}