Add database schema

This commit is contained in:
gpt-engineer-app[bot]
2025-10-21 04:23:22 +00:00
parent 0570846b00
commit f9feb67425
15 changed files with 1620 additions and 4 deletions

View File

@@ -0,0 +1,175 @@
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.3';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
const supabase = createClient(supabaseUrl, supabaseKey);
console.log('Starting price check...');
// Get all enabled alert profiles
const { data: alertProfiles, error: profilesError } = await supabase
.from('alert_profiles')
.select('*')
.eq('enabled', true);
if (profilesError) {
throw profilesError;
}
console.log(`Found ${alertProfiles?.length || 0} active alert profiles`);
// Get all products
const { data: products, error: productsError } = await supabase
.from('products')
.select('*');
if (productsError) {
throw productsError;
}
console.log(`Found ${products?.length || 0} products`);
const alerts = [];
// Check each alert profile against products
for (const profile of alertProfiles || []) {
for (const product of products || []) {
// Match category
if (product.category !== profile.category) continue;
// Check if product meets specification requirements
const specsFilter = profile.specs_filter;
const productSpecs = product.specs;
let meetsRequirements = true;
// Check minimum size (for harddrives/ram)
if (specsFilter.min_size) {
const minSizeValue = parseFloat(specsFilter.min_size);
const productSizeValue = parseFloat(productSpecs.size || '0');
if (productSizeValue < minSizeValue) {
meetsRequirements = false;
}
}
// Check type (SSD/HDD/etc)
if (specsFilter.type && productSpecs.type !== specsFilter.type) {
meetsRequirements = false;
}
// Check if price is below target
if (!meetsRequirements || product.current_price > profile.target_price) {
continue;
}
// Found a match! Log the alert
const message = `🎯 Alert: ${profile.name}\n` +
`Product: ${product.name}\n` +
`Price: ${product.current_price} ${product.currency}\n` +
`Target: ${profile.target_price} ${product.currency}\n` +
`Store: ${product.store}\n` +
`URL: ${product.url}`;
const { error: logError } = await supabase
.from('alerts_log')
.insert({
alert_profile_id: profile.id,
product_id: product.id,
triggered_price: product.current_price,
message,
sent_to_discord: false
});
if (logError) {
console.error('Error logging alert:', logError);
}
alerts.push({
profile,
product,
message
});
// Send Discord notification if webhook URL is set
if (profile.discord_webhook_url) {
try {
await fetch(profile.discord_webhook_url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: message,
embeds: [{
title: '💰 Price Alert!',
description: message,
color: 0x00ff00,
fields: [
{
name: 'Current Price',
value: `${product.current_price} ${product.currency}`,
inline: true
},
{
name: 'Target Price',
value: `${profile.target_price} ${product.currency}`,
inline: true
},
{
name: 'Savings',
value: `${(profile.target_price - product.current_price).toFixed(2)} ${product.currency}`,
inline: true
}
],
timestamp: new Date().toISOString()
}]
})
});
// Update log to mark as sent
await supabase
.from('alerts_log')
.update({ sent_to_discord: true })
.eq('alert_profile_id', profile.id)
.eq('product_id', product.id);
console.log(`Sent Discord notification for ${product.name}`);
} catch (discordError) {
console.error('Error sending Discord notification:', discordError);
}
}
}
}
console.log(`Price check complete. Found ${alerts.length} matching alerts`);
return new Response(
JSON.stringify({
success: true,
alertsTriggered: alerts.length,
alerts
}),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('Error in check-prices function:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return new Response(
JSON.stringify({ error: errorMessage }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
);
}
});

View File

@@ -0,0 +1,83 @@
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const { webhookUrl, message, product, profile } = await req.json();
if (!webhookUrl) {
throw new Error('Discord webhook URL is required');
}
const embed = {
title: '💰 Price Alert Triggered!',
description: message,
color: 0x00ff00,
fields: [
{
name: 'Product',
value: product.name,
inline: false
},
{
name: 'Current Price',
value: `${product.current_price} ${product.currency}`,
inline: true
},
{
name: 'Target Price',
value: `${profile.target_price} ${product.currency}`,
inline: true
},
{
name: 'Store',
value: product.store,
inline: true
},
{
name: 'Link',
value: product.url,
inline: false
}
],
timestamp: new Date().toISOString()
};
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: `🎯 **${profile.name}** - Price target reached!`,
embeds: [embed]
})
});
if (!response.ok) {
throw new Error(`Discord API error: ${response.status}`);
}
console.log('Discord notification sent successfully');
return new Response(
JSON.stringify({ success: true }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('Error sending Discord alert:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return new Response(
JSON.stringify({ error: errorMessage }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
);
}
});