Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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