From 75de0ee866b5ec9aed827468c9d111e7b0f83903 Mon Sep 17 00:00:00 2001 From: mahula Date: Mon, 19 Jan 2026 15:22:34 +0100 Subject: [PATCH] fix: auto-generate tag seed data from hashtags in items.json Extend prepare-seed.js to extract hashtags from item text fields and generate tags.json automatically during the seeding process. This ensures hashtag click filtering works correctly in the application. Changes: - Extract all #hashtag patterns from items.json text fields - Generate tags.json with unique tags and auto-assigned colors - Preserve existing tag colors when re-running the script - Add tags.json to .gitignore (auto-generated file) The script runs as part of seed.sh, so CI/CD pipelines and local development will automatically have matching tags for all hashtags used in seed data items. --- .gitignore | 2 + backend/prepare-seed.js | 168 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 153 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 5a4915fb..24dc9f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ cypress/results/ cypress/runner-results/ cypress/screenshots/ +# Generated seed data (auto-generated from items.json by prepare-seed.js) +backend/directus-config/development/seed/tags.json diff --git a/backend/prepare-seed.js b/backend/prepare-seed.js index 94f025df..fb47be79 100644 --- a/backend/prepare-seed.js +++ b/backend/prepare-seed.js @@ -1,11 +1,19 @@ #!/usr/bin/env node /** - * Prepares seed data by updating event dates relative to the current date. + * Prepares seed data for development environment. * - * This script modifies items.json in-place, updating all items with - * layer "layer-events" to have realistic start/end dates that ensure - * events are visible in the app (end dates in the future). + * This script performs two main tasks: + * + * 1. Updates event dates relative to the current date: + * - Modifies items.json in-place, updating all items with layer "layer-events" + * - Ensures events are visible in the app (end dates in the future) + * + * 2. Extracts hashtags from items and generates tags.json: + * - Parses all #hashtag patterns from text fields in items.json + * - Preserves existing tag colors from tags.json if present + * - Generates new colors for new hashtags + * - Ensures hashtag filtering works in the application * * Date strategy: * - Event 1 (item-event-1): Long-running, started 30 days ago, ends in 365 days @@ -17,7 +25,9 @@ const fs = require('fs') const path = require('path') -const seedPath = path.join(__dirname, 'directus-config/development/seed/items.json') +const seedDir = path.join(__dirname, 'directus-config/development/seed') +const seedPath = path.join(seedDir, 'items.json') +const tagsPath = path.join(seedDir, 'tags.json') function addDays(date, days) { const result = new Date(date) @@ -35,6 +45,91 @@ function formatDateTime(date) { return date.toISOString().slice(0, 19) } +/** + * Color palette for auto-generated tag colors. + * Uses modern, accessible colors with good contrast. + */ +const TAG_COLORS = [ + '#22C55E', // green + '#3B82F6', // blue + '#EAB308', // yellow + '#F97316', // orange + '#EC4899', // pink + '#8B5CF6', // purple + '#06B6D4', // cyan + '#14B8A6', // teal + '#F43F5E', // rose + '#84CC16', // lime + '#A855F7', // violet + '#0EA5E9', // sky + '#10B981', // emerald + '#EF4444', // red + '#64748B', // slate +] + +/** + * Extract all unique hashtags from items.json text fields + */ +function extractHashtags(items) { + const hashtags = new Set() + const hashtagRegex = /#([a-zA-Z0-9_-]+)/g + + for (const item of items) { + if (item.text) { + let match + while ((match = hashtagRegex.exec(item.text)) !== null) { + hashtags.add(match[1].toLowerCase()) + } + } + } + + return Array.from(hashtags).sort() +} + +/** + * Load existing tags.json to preserve colors + */ +function loadExistingTags() { + try { + if (fs.existsSync(tagsPath)) { + const content = fs.readFileSync(tagsPath, 'utf8') + const tagsData = JSON.parse(content) + const colorMap = {} + for (const tag of tagsData.data || []) { + colorMap[tag.name.toLowerCase()] = tag.color + } + return colorMap + } + } catch (error) { + console.warn(' Warning: Could not read existing tags.json:', error.message) + } + return {} +} + +/** + * Generate tags.json from extracted hashtags + */ +function generateTagsSeed(hashtags, existingColors) { + const data = hashtags.map((name, index) => ({ + _sync_id: `tag-${name}`, + name: name, + color: existingColors[name] || TAG_COLORS[index % TAG_COLORS.length], + })) + + return { + collection: 'tags', + meta: { + insert_order: 0, + create: true, + update: true, + delete: true, + preserve_ids: false, + ignore_on_update: [], + }, + data: data, + } +} + /** * Calculate dynamic dates for each event based on current time */ @@ -65,15 +160,14 @@ function getEventDates(syncId, now) { return dateConfigs[syncId] || null } -function prepareSeedData() { - // Read the current items.json - const content = fs.readFileSync(seedPath, 'utf8') - const seedData = JSON.parse(content) - +/** + * Update event dates in items.json + */ +function updateEventDates(seedData) { const now = new Date() let updatedCount = 0 - // Update event items with dynamic dates + console.log('Updating event dates:') for (const item of seedData.data) { if (item.layer === 'layer-events' && item._sync_id) { const dates = getEventDates(item._sync_id, now) @@ -88,12 +182,52 @@ function prepareSeedData() { } } - // Write back to items.json - fs.writeFileSync(seedPath, JSON.stringify(seedData, null, 4)) - - console.log(`\nUpdated ${updatedCount} event(s) with dynamic dates.`) + console.log(` Updated ${updatedCount} event(s) with dynamic dates.\n`) } -console.log('Preparing seed data with dynamic dates...\n') -prepareSeedData() +/** + * Generate tags.json from hashtags in items.json + */ +function updateTagsSeed(seedData) { + console.log('Generating tags from hashtags:') + // Extract hashtags from items + const hashtags = extractHashtags(seedData.data) + console.log(` Found ${hashtags.length} unique hashtag(s): ${hashtags.join(', ')}`) + + // Load existing colors to preserve them + const existingColors = loadExistingTags() + const preservedCount = Object.keys(existingColors).filter((name) => hashtags.includes(name)).length + if (preservedCount > 0) { + console.log(` Preserving colors for ${preservedCount} existing tag(s)`) + } + + // Generate and write tags.json + const tagsSeed = generateTagsSeed(hashtags, existingColors) + fs.writeFileSync(tagsPath, JSON.stringify(tagsSeed, null, 4)) + console.log(` Written ${hashtags.length} tag(s) to tags.json\n`) +} + +/** + * Main entry point + */ +function prepareSeedData() { + console.log('Preparing seed data...\n') + + // Read the current items.json + const content = fs.readFileSync(seedPath, 'utf8') + const seedData = JSON.parse(content) + + // 1. Update event dates + updateEventDates(seedData) + + // Write back to items.json (with updated dates) + fs.writeFileSync(seedPath, JSON.stringify(seedData, null, 4)) + + // 2. Generate tags.json from hashtags + updateTagsSeed(seedData) + + console.log('Done!') +} + +prepareSeedData()