Update Version Database and Send Notifications #46
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Update Version Database and Send Notifications | |
on: | |
# Run after the build-app-packages workflow completes | |
workflow_run: | |
workflows: ["Build App Packages and Collect App Information"] | |
types: | |
- completed | |
# Allow manual triggering | |
workflow_dispatch: | |
jobs: | |
update-database: | |
runs-on: ubuntu-latest | |
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Set up Node.js | |
uses: actions/setup-node@v3 | |
with: | |
node-version: "18" | |
- name: Create package.json and install dependencies | |
run: | | |
# Create a minimal package.json file | |
echo '{"name":"version-tracker","type":"module","private":true}' > package.json | |
# Install dependencies | |
npm install @supabase/supabase-js node-fetch | |
- name: Create version tracking script | |
run: | | |
cat > update-versions.js << 'EOF' | |
import { createClient } from '@supabase/supabase-js'; | |
import fs from 'fs/promises'; | |
import path from 'path'; | |
import fetch from 'node-fetch'; | |
// Configuration | |
const SUPABASE_URL = process.env.SUPABASE_URL; | |
const SUPABASE_KEY = process.env.SUPABASE_KEY; // This must be the service role key, not the anon key | |
const NOTIFICATIONS_API_URL = process.env.NOTIFICATIONS_API_URL || 'https://intunebrew.com/api/notifications/send'; | |
const NOTIFICATIONS_API_KEY = process.env.NOTIFICATIONS_API_KEY; | |
// Initialize Supabase client | |
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); | |
// Function to read app JSON files | |
async function readAppFiles() { | |
try { | |
console.log('Reading app JSON files...'); | |
const appsDir = path.join(process.cwd(), 'Apps'); | |
const files = await fs.readdir(appsDir); | |
const appData = []; | |
for (const file of files) { | |
if (file.endsWith('.json')) { | |
try { | |
const filePath = path.join(appsDir, file); | |
const content = await fs.readFile(filePath, 'utf8'); | |
const data = JSON.parse(content); | |
// Extract app name from filename (remove .json extension) | |
const appName = file.replace('.json', ''); | |
appData.push({ | |
app_name: appName, | |
display_name: data.name || data.display_name || appName, | |
version: data.version || 'unknown', | |
description: data.description || '', | |
changelog: data.changelog || '', | |
homepage: data.homepage || '', | |
url: data.url || '', | |
}); | |
} catch (fileError) { | |
console.error(`Error reading file ${file}:`, fileError); | |
} | |
} | |
} | |
console.log(`Found ${appData.length} app files`); | |
return appData; | |
} catch (error) { | |
console.error('Error reading app files:', error); | |
return []; | |
} | |
} | |
// Function to get existing versions from database | |
async function getExistingVersions() { | |
try { | |
console.log('Fetching existing versions from database...'); | |
const { data, error } = await supabase | |
.from('app_versions') | |
.select('*'); | |
if (error) { | |
console.error('Error fetching versions:', error); | |
return []; | |
} | |
console.log(`Found ${data.length} existing version records`); | |
return data; | |
} catch (error) { | |
console.error('Error getting existing versions:', error); | |
return []; | |
} | |
} | |
// Function to send notification for an app update | |
async function sendNotification(appName, version, changelog) { | |
try { | |
console.log(`Sending notification for ${appName} ${version}...`); | |
const response = await fetch(NOTIFICATIONS_API_URL, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${NOTIFICATIONS_API_KEY}`, | |
'X-Clerk-Secret-Key': process.env.CLERK_SECRET_KEY // Pass the Clerk Secret Key to the API | |
}, | |
body: JSON.stringify({ | |
appName, | |
version, | |
changelog | |
}) | |
}); | |
const data = await response.json(); | |
if (response.ok) { | |
console.log(`✅ Notification sent for ${appName} ${version}`); | |
// Extract the number of users notified from the message if available | |
const userCountMatch = data.message?.match(/Notification sent to (\d+) of (\d+) users/); | |
if (userCountMatch) { | |
console.log(`Notified ${userCountMatch[1]} of ${userCountMatch[2]} subscribed users`); | |
} else { | |
console.log(`Summary: ${data.message || 'Notification processed successfully'}`); | |
} | |
return true; | |
} else { | |
// Handle the case where no users were found in Clerk | |
if (data.message && data.message.includes("No valid users found with Clerk")) { | |
console.warn(`⚠️ No valid users found in Clerk for ${appName}. This is expected if running in a GitHub workflow environment without Clerk access.`); | |
console.warn(`To receive notifications, users need to subscribe in the IntuneBrew website.`); | |
return true; // Consider this a success since it's an expected limitation | |
} else { | |
console.error(`❌ Failed to send notification for ${appName}`); | |
// Only log the error message, not the full error data | |
console.error(`Error: ${data.error || 'Unknown error'}`); | |
return false; | |
} | |
} | |
} catch (error) { | |
console.error(`Error sending notification for ${appName}:`, error); | |
return false; | |
} | |
} | |
// Function to send notification for multiple app updates | |
async function sendBatchNotification(updates) { | |
try { | |
if (updates.length === 0) { | |
console.log('No updates to notify about'); | |
return true; | |
} | |
console.log(`Sending batch notification for ${updates.length} app updates...`); | |
const response = await fetch(NOTIFICATIONS_API_URL, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${NOTIFICATIONS_API_KEY}`, | |
'x-clerk-secret-key': process.env.CLERK_SECRET_KEY || '' | |
}, | |
body: JSON.stringify({ | |
updates: updates | |
}) | |
}); | |
const data = await response.json(); | |
if (response.ok) { | |
console.log(`✅ Batch notification sent for ${updates.length} app updates`); | |
// Extract the number of users notified from the message if available | |
const userCountMatch = data.message?.match(/Notification sent to (\d+) of (\d+) users/); | |
return true; | |
} else { | |
// Handle the case where no users were found in Clerk | |
if (data.message && data.message.includes("No valid users found with Clerk")) { | |
console.warn(`⚠️ No valid users found in Clerk. This is expected if running in a GitHub workflow environment without Clerk access.`); | |
console.warn(`To receive notifications, users need to subscribe in the IntuneBrew website.`); | |
return true; // Consider this a success since it's an expected limitation | |
} else { | |
console.error(`❌ Failed to send batch notification`); | |
// Only log the error message, not the full error data | |
console.error(`Error: ${data.error || 'Unknown error'}`); | |
return false; | |
} | |
} | |
} catch (error) { | |
console.error(`Error sending batch notification:`, error); | |
return false; | |
} | |
} | |
// Main function to update versions and send notifications | |
async function updateVersionsAndNotify() { | |
try { | |
// Get current app data from JSON files | |
const appData = await readAppFiles(); | |
if (appData.length === 0) { | |
console.log('No app data found, exiting'); | |
return; | |
} | |
// Get existing versions from database | |
const existingVersions = await getExistingVersions(); | |
// Group existing versions by app_name | |
const versionsByApp = {}; | |
for (const version of existingVersions) { | |
if (!versionsByApp[version.app_name]) { | |
versionsByApp[version.app_name] = []; | |
} | |
versionsByApp[version.app_name].push(version); | |
} | |
// Collect all app updates | |
const appUpdates = []; | |
// Process each app | |
for (const app of appData) { | |
console.log(`Processing ${app.display_name || app.app_name}...`); | |
const appVersions = versionsByApp[app.app_name] || []; | |
// Sort versions by created_at (newest first) | |
appVersions.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); | |
// Check if this is a new version | |
const isNewVersion = appVersions.length === 0 || | |
!appVersions.some(v => v.version === app.version); | |
if (isNewVersion) { | |
console.log(`New version detected for ${app.app_name}: ${app.version}`); | |
// Get previous version if available | |
const previousVersion = appVersions.length > 0 ? appVersions[0].version : null; | |
// Insert the new version | |
const { error: insertError } = await supabase | |
.from('app_versions') | |
.insert({ | |
app_name: app.app_name, | |
version: app.version, | |
display_name: app.display_name, | |
description: app.description, | |
changelog: app.changelog, | |
homepage: app.homepage, | |
url: app.url, | |
previous_version: previousVersion | |
}); | |
if (insertError) { | |
console.error(`Error inserting version for ${app.app_name}:`, insertError); | |
continue; | |
} | |
console.log(`✅ Added version ${app.version} for ${app.app_name}`); | |
// If we have a previous version, add to updates list | |
if (appVersions.length > 0) { | |
const previousVersion = appVersions[0]; | |
console.log(`Previous version: ${previousVersion.version}`); | |
// Add to updates list | |
appUpdates.push({ | |
appName: app.app_name, | |
version: app.version, | |
changelog: app.changelog | |
}); | |
} | |
// If we now have more than 2 versions, delete the oldest ones | |
if (appVersions.length >= 2) { | |
// Keep only the 2 newest versions (the one we just added + the previous one) | |
const versionsToDelete = appVersions.slice(1); // Skip the most recent one | |
for (const oldVersion of versionsToDelete) { | |
console.log(`Deleting old version record: ${app.app_name} ${oldVersion.version}`); | |
const { error: deleteError } = await supabase | |
.from('app_versions') | |
.delete() | |
.eq('id', oldVersion.id); | |
if (deleteError) { | |
console.error(`Error deleting old version for ${app.app_name}:`, deleteError); | |
} | |
} | |
} | |
} else { | |
console.log(`No new version for ${app.app_name}, current: ${app.version}`); | |
// Update the existing version record with any new information | |
const existingVersion = appVersions.find(v => v.version === app.version); | |
if (existingVersion) { | |
const { error: updateError } = await supabase | |
.from('app_versions') | |
.update({ | |
display_name: app.display_name, | |
description: app.description, | |
changelog: app.changelog, | |
homepage: app.homepage, | |
url: app.url, | |
last_checked: new Date().toISOString() | |
}) | |
.eq('id', existingVersion.id); | |
if (updateError) { | |
console.error(`Error updating version for ${app.app_name}:`, updateError); | |
} | |
} | |
} | |
} | |
// Send batch notification for all updates | |
if (appUpdates.length > 0) { | |
console.log(`Found ${appUpdates.length} app updates to notify about`); | |
await sendBatchNotification(appUpdates); | |
} else { | |
console.log('No new app updates to notify about'); | |
} | |
console.log('Version database update completed'); | |
} catch (error) { | |
console.error('Error in updateVersionsAndNotify:', error); | |
} | |
} | |
// Run the update | |
updateVersionsAndNotify(); | |
EOF | |
- name: Run version tracking script | |
run: node update-versions.js | |
env: | |
# Supabase configuration - must use service role key for database operations | |
SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | |
SUPABASE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
# Notification API configuration | |
NOTIFICATIONS_API_URL: ${{ secrets.NOTIFICATIONS_API_URL }} | |
NOTIFICATIONS_API_KEY: ${{ secrets.NOTIFICATIONS_API_KEY }} | |
# Clerk API configuration | |
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} |