Skip to content
Draft
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ __pycache__

# Local Netlify folder
.netlify/*
!.netlify/functions

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
6 changes: 3 additions & 3 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
access-control-allow-origin = "*"

[functions]
directory = ".netlify/functions/"
directory = "netlify/functions/"

## Remove /current from the path
## Remove /current from the path
[[redirects]]
from = "/weaviate/current/*"
to = "/weaviate/:splat"
Expand Down Expand Up @@ -853,7 +853,7 @@ from = "/academy/*"
to = "https://academy.weaviate.io/"
status = 301

## AWS production guides
## AWS production guides

[[redirects]]
from = "/deploy/aws"
Expand Down
File renamed without changes.
193 changes: 193 additions & 0 deletions netlify/functions/submit-feedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
exports.handler = async (event) => {
const allowedOriginPattern = process.env.ALLOWED_ORIGIN || '*';
const requestOrigin = event.headers.origin;

const isAllowed = () => {
if (allowedOriginPattern === '*') {
return true;
}
if (!requestOrigin) {
return false;
}
if (requestOrigin === allowedOriginPattern) {
return true;
}
if (allowedOriginPattern.startsWith('*.')) {
const baseDomain = allowedOriginPattern.substring(1);
const requestHostname = new URL(requestOrigin).hostname;
return requestHostname.endsWith(baseDomain);
}
return false;
};

if (!isAllowed()) {
return {
statusCode: 403,
body: JSON.stringify({ error: 'Origin not allowed' }),
headers: {
'Access-Control-Allow-Origin': allowedOriginPattern === '*'
? '*'
: allowedOriginPattern,
},
};
}

const accessControlOrigin = allowedOriginPattern.startsWith('*.')
? requestOrigin
: allowedOriginPattern;

const headers = {
'Access-Control-Allow-Origin': accessControlOrigin,
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};

// Handle preflight CORS request for browser compatibility
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 204,
headers,
body: '',
};
}

// We only want to handle POST requests
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
body: 'Method Not Allowed',
headers,
};
}

try {
const data = JSON.parse(event.body);
const { WEAVIATE_DOCFEEDBACK_URL, WEAVIATE_DOCFEEDBACK_API_KEY } =
process.env;

// Basic server-side validation
if (!data.page || typeof data.isPositive !== 'boolean') {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Missing required fields: page and isPositive.',
}),
headers,
};
}

if (!WEAVIATE_DOCFEEDBACK_URL || !WEAVIATE_DOCFEEDBACK_API_KEY) {
// const relevantKeys = Object.keys(process.env).filter(
// k => k.includes('WEAVIATE') || k.includes('FEEDBACK') || k === 'CONTEXT' || k === 'ALLOWED_ORIGIN' || k.startsWith('DEPLOY_')
// );
console.error('Missing Weaviate environment variables.', {
hasUrl: !!WEAVIATE_DOCFEEDBACK_URL,
hasKey: !!WEAVIATE_DOCFEEDBACK_API_KEY,
context: process.env.CONTEXT,
weaviateRelatedKeyNames: relevantKeys,
weaviateRelatedKeyCount: relevantKeys.length,
});
return {
statusCode: 500,
body: JSON.stringify({
error: 'Server configuration error.',
// debug: {
// hasUrl: !!WEAVIATE_DOCFEEDBACK_URL,
// hasKey: !!WEAVIATE_DOCFEEDBACK_API_KEY,
// context: process.env.CONTEXT,
// availableWeaviateVars: relevantKeys,
// availableWeaviateVarCount: relevantKeys.length,
// siteId: process.env.SITE_ID,
// siteName: process.env.SITE_NAME,
// timestamp: new Date().toISOString(),
// },
}),
headers,
};
}

const weaviatePayload = {
class: 'DocFeedback',
properties: {
page: data.page,
isPositive: data.isPositive,
// The frontend sends an array of option indexes as integers
options: data.options || [],
// The frontend can send 'comment' (singular).
comments: data.comment,
timestamp: new Date().toISOString(),
testData: data.testData, // Add the testData flag
hostname: data.hostname, // Add the hostname
},
};

const weaviateUrl = `${WEAVIATE_DOCFEEDBACK_URL}/v1/objects`;

const response = await fetch(weaviateUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${WEAVIATE_DOCFEEDBACK_API_KEY}`,
},
body: JSON.stringify(weaviatePayload),
});

if (!response.ok) {
let errorBody;
try {
errorBody = await response.json();
} catch (jsonError) {
// If response is not JSON, get it as text
try {
errorBody = await response.text();
} catch (textError) {
errorBody = 'Unable to parse error response';
}
}
console.error('Failed to send data to Weaviate:', {
status: response.status,
statusText: response.statusText,
body: errorBody,
});
return {
statusCode: response.status,
body: JSON.stringify({
error: 'Failed to store feedback.',
debug: {
weaviateStatus: response.status,
weaviateStatusText: response.statusText,
weaviateUrl: WEAVIATE_DOCFEEDBACK_URL,
// TODO: Remove errorBody from production after debugging
weaviateError: errorBody,
timestamp: new Date().toISOString(),
context: process.env.CONTEXT,
},
}),
headers,
};
}

return {
statusCode: 200,
body: JSON.stringify({ message: 'Feedback received and stored.' }),
headers,
};
} catch (error) {
console.error('Error processing feedback:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'There was an error processing your feedback.',
debug: {
errorType: error.name,
errorMessage: error.message,
timestamp: new Date().toISOString(),
// TODO: Remove stack trace from production after debugging
stack: error.stack,
context: process.env.CONTEXT,
},
}),
headers,
};
}
};
2 changes: 1 addition & 1 deletion src/components/PageRatingWidget/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function PageRatingWidget() {
}

try {
const response = await fetch('/.netlify/functions/submit-feedback', {
const response = await fetch('/netlify/functions/submit-feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
Loading