Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,22 @@ export default defineHook(({ filter }, hookContext) => {
}`
)

// Get the new payload with slug
const newPayload = await getPayloadWithSlug(futureItem, { metadata, payload })

// For podcasts: save old slug to history if the podcast is published and slug is changing
if (
metadata.collection === 'podcasts' &&
item.status === 'published' &&
item.slug &&
newPayload.slug &&
item.slug !== newPayload.slug
) {
await saveSlugToHistory(item.id, item.slug, context)
}

// Return payload with "slug"
return await getPayloadWithSlug(futureItem, { metadata, payload })
return newPayload
}
}

Expand All @@ -93,7 +107,44 @@ export default defineHook(({ filter }, hookContext) => {
return payload
}




/**
* Saves the old slug to the podcast_slug_history collection.
* This allows old URLs to redirect to the new slug.
*
* @param podcastId The ID of the podcast
* @param oldSlug The old slug being replaced
* @param context The hook context
*/
async function saveSlugToHistory(podcastId: string, oldSlug: string, context: { accountability: any; schema: any }) {
try {
const slugHistoryService = new ItemsService('podcast_slug_history', {
accountability: context.accountability,
schema: context.schema,
})

// Check if this slug already exists in history for this podcast
const existingEntries = await slugHistoryService.readByQuery({
filter: {
podcast: { _eq: podcastId },
old_slug: { _eq: oldSlug },
},
limit: 1,
})

// Only create a new entry if this slug isn't already in history
if (!existingEntries || existingEntries.length === 0) {
await slugHistoryService.createOne({
podcast: podcastId,
old_slug: oldSlug,
})

logger.info(`${HOOK_NAME} hook: Saved old slug "${oldSlug}" to history for podcast "${podcastId}"`)
} else {
logger.info(`${HOOK_NAME} hook: Slug "${oldSlug}" already exists in history for podcast "${podcastId}"`)
}
} catch (error: any) {
// Log but don't fail the entire operation if slug history save fails
logger.error(`${HOOK_NAME} hook: Failed to save slug to history: ${error.message}`)
}
}
})
241 changes: 241 additions & 0 deletions directus-cms/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,34 @@
"schema": {
"name": "ticket_settings"
}
},
{
"collection": "podcast_slug_history",
"meta": {
"accountability": "all",
"archive_app_filter": true,
"archive_field": null,
"archive_value": null,
"collapse": "open",
"collection": "podcast_slug_history",
"color": null,
"display_template": "{{old_slug}}",
"group": "Podcasts",
"hidden": false,
"icon": "history",
"item_duplication_fields": null,
"note": "History of old podcast slugs for redirect purposes",
"preview_url": null,
"singleton": false,
"sort": 2,
"sort_field": null,
"translations": null,
"unarchive_value": null,
"versioning": false
},
"schema": {
"name": "podcast_slug_history"
}
}
],
"fields": [
Expand Down Expand Up @@ -34398,6 +34426,194 @@
"schema": {
"is_indexed": true
}
},
{
"collection": "podcast_slug_history",
"field": "id",
"type": "uuid",
"meta": {
"collection": "podcast_slug_history",
"conditions": null,
"display": null,
"display_options": null,
"field": "id",
"group": null,
"hidden": true,
"interface": "input",
"note": null,
"options": null,
"readonly": true,
"required": false,
"searchable": true,
"sort": 1,
"special": [
"uuid"
],
"translations": null,
"validation": null,
"validation_message": null,
"width": "full"
},
"schema": {
"name": "id",
"table": "podcast_slug_history",
"data_type": "uuid",
"default_value": null,
"max_length": null,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": true,
"is_indexed": false,
"is_primary_key": true,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
},
{
"collection": "podcast_slug_history",
"field": "podcast",
"type": "uuid",
"meta": {
"collection": "podcast_slug_history",
"conditions": null,
"display": "related-values",
"display_options": {
"template": "{{title}}"
},
"field": "podcast",
"group": null,
"hidden": false,
"interface": "select-dropdown-m2o",
"note": "The podcast this slug belonged to",
"options": {
"template": "{{title}}"
},
"readonly": false,
"required": true,
"searchable": true,
"sort": 2,
"special": [
"m2o"
],
"translations": null,
"validation": null,
"validation_message": null,
"width": "full"
},
"schema": {
"name": "podcast",
"table": "podcast_slug_history",
"data_type": "uuid",
"default_value": null,
"max_length": null,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": false,
"is_indexed": true,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": "podcasts",
"foreign_key_column": "id"
}
},
{
"collection": "podcast_slug_history",
"field": "old_slug",
"type": "string",
"meta": {
"collection": "podcast_slug_history",
"conditions": null,
"display": null,
"display_options": null,
"field": "old_slug",
"group": null,
"hidden": false,
"interface": "input",
"note": "The previous slug that should redirect to the current one",
"options": null,
"readonly": false,
"required": true,
"searchable": true,
"sort": 3,
"special": null,
"translations": null,
"validation": null,
"validation_message": null,
"width": "full"
},
"schema": {
"name": "old_slug",
"table": "podcast_slug_history",
"data_type": "varchar",
"default_value": null,
"max_length": 255,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": false,
"is_indexed": true,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
},
{
"collection": "podcast_slug_history",
"field": "date_created",
"type": "timestamp",
"meta": {
"collection": "podcast_slug_history",
"conditions": null,
"display": "datetime",
"display_options": {
"relative": true
},
"field": "date_created",
"group": null,
"hidden": false,
"interface": "datetime",
"note": null,
"options": null,
"readonly": true,
"required": false,
"searchable": true,
"sort": 4,
"special": [
"date-created"
],
"translations": null,
"validation": null,
"validation_message": null,
"width": "half"
},
"schema": {
"name": "date_created",
"table": "podcast_slug_history",
"data_type": "timestamp with time zone",
"default_value": null,
"max_length": null,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": true,
"is_unique": false,
"is_indexed": false,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
}
],
"relations": [
Expand Down Expand Up @@ -37891,6 +38107,31 @@
"on_delete": "CASCADE",
"constraint_name": null
}
},
{
"collection": "podcast_slug_history",
"field": "podcast",
"related_collection": "podcasts",
"meta": {
"junction_field": null,
"many_collection": "podcast_slug_history",
"many_field": "podcast",
"one_allowed_collections": null,
"one_collection": "podcasts",
"one_collection_field": null,
"one_deselect_action": "nullify",
"one_field": null,
"sort_field": null
},
"schema": {
"table": "podcast_slug_history",
"column": "podcast",
"foreign_key_table": "podcasts",
"foreign_key_column": "id",
"on_update": "NO ACTION",
"on_delete": "CASCADE",
"constraint_name": null
}
}
]
}
31 changes: 31 additions & 0 deletions nuxt-app/composables/useDirectus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,36 @@ export function useDirectus() {
}
}

/**
* Get all podcast slug history entries for building redirects.
* Returns a list of old slugs mapped to their current podcast slugs.
*/
async function getPodcastSlugHistory(): Promise<{ oldSlug: string; currentSlug: string }[]> {
interface SlugHistoryEntry {
old_slug: string
podcast: { slug: string } | null
}

return await directus
.request(
readItems('podcast_slug_history', {
fields: [
'old_slug',
'podcast.slug',
],
limit: -1,
})
)
.then((result) =>
(result as SlugHistoryEntry[])
.filter((entry) => entry.podcast?.slug && entry.old_slug !== entry.podcast.slug)
.map((entry) => ({
oldSlug: entry.old_slug,
currentSlug: entry.podcast!.slug,
}))
)
}

return {
getHomepage,
getPodcastPage,
Expand Down Expand Up @@ -918,5 +948,6 @@ export function useDirectus() {
getTestimonials,
createRating,
getTicketSettings,
getPodcastSlugHistory,
}
}
Loading