Skip to content

Instantly share code, notes, and snippets.

@DanRibbens
Last active September 1, 2024 18:52
Show Gist options
  • Save DanRibbens/438a64157553c5d954e3da3d579f0808 to your computer and use it in GitHub Desktop.
Save DanRibbens/438a64157553c5d954e3da3d579f0808 to your computer and use it in GitHub Desktop.
Payload Many to Many Relationships example
import type { CollectionConfig } from 'payload/types'
export const Episodes: CollectionConfig {
slug: 'episodes',
admin: {
useAsTitle: 'title',
},
access: {
read: () => true,
},
hooks: {
afterDelete: [
async ({context, req, id, doc}) => {
// set a flag in the context to prevent any updates to series having an effect on the episodes
if (context.hasUpdatedEpisodes) return
context.hasUpdatedEpisodes = true
if (!doc.series || doc.series.length === 0) return
// all the series that have the episode being deleted need to be updated
await updateSeriesEpisodes({
req,
seriesIDsAddingEpisode: [],
seriesIDsRemovingEpisode: doc.series.map(({id}) => id),
episodeID: doc.id,
context,
})
}
],
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'series',
type: 'relationship',
relationTo: 'series',
hasMany: true,
hooks: {
afterChange: [async ({ value, req, previousValue = [], originalDoc, context }) => {
// set a flag in the context to prevent any updates to series having an effect on the episodes
if (context.hasUpdatedSeriesAfterChange) return
context.hasUpdatedEpisodesAfterChange = true
const previousIDs = previousValue?.map((episode) => episode) || []
const currentIDs = value?.map((episode) => episode) || []
const seriesIDsAddingEpisode = currentIDs.reduce((ids, episode) => {
if (!previousIDs.includes(episode)) {
ids.push(episode)
}
return ids
}, [])
const seriesIDsRemovingEpisode = previousIDs.reduce((ids, episode) => {
if (!currentIDs.includes(episode)) {
ids.push(episode)
}
return ids
}, [])
await updateSeriesEpisodes({
req,
seriesIDsAddingEpisode,
seriesIDsRemovingEpisode,
episodeID: originalDoc.id,
context,
})
}]
}
},
]
}
query episodesBySeries {
Episodes(where:{series: {equals: "65fc56c345ba752915613d5f"}}) {
docs {
id
title
}
}
}
query seriesByEpisode {
allSeries(where: { episodes__episode: { equals: "65fc545c1b3f4ed66e2fb4b2"}}) {
docs {
id
episodes {
episode {
title
}
}
}
}
}
import type { CollectionConfig } from 'payload/types'
export const Series: CollectionConfig = {
slug: 'series',
access: {
read: () => true,
},
hooks: {
afterDelete: [
async ({context, req, id, doc}) => {
// set a flag in the context to prevent any updates to series having an effect on the episodes
if (context.hasUpdatedEpisodes) return
context.hasUpdatedEpisodes = true
// all the episodes that have the series being deleted need to be updated
const episodeIDsRemovingSeries = doc.episodes.map(({episode}) => episode?.id).filter(Boolean)
await updateEpisodesSeries({
req,
episodeIDsAddedToSeries: [],
episodeIDsRemovingSeries,
seriesID: id,
context,
})
}
],
},
admin: {
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'episodes',
type: 'array',
hooks: {
afterChange: [
async ({ value = [], previousValue = [], context, req, originalDoc }) => {
// set a flag in the context to prevent any updates to series having an effect on the episodes
if (context.hasUpdatedEpisodesAfterChange) return
context.hasUpdatedSeriesAfterChange = true
// get the previous and current series IDs
const previousIDs = previousValue?.map(({episode}) => episode) || []
const currentIDs = value?.map(({episode}) => episode) || []
const episodeIDsAddedToSeries = currentIDs.reduce((ids, episode) => {
if (!previousIDs.includes(episode)) {
ids.push(episode)
}
return ids
}, [])
const episodeIDsRemovingSeries = previousIDs.reduce((ids, episode) => {
if (!currentIDs.includes(episode)) {
ids.push(episode)
}
return ids
}, [])
await updateEpisodesSeries({
req,
episodeIDsAddedToSeries,
episodeIDsRemovingSeries,
seriesID: originalDoc.id,
context,
})
}
],
},
fields: [
{
name: 'episode',
type: 'relationship',
relationTo: 'episodes',
},
],
}],
},
export const updateEpisodesSeries = async ({
req,
context,
episodeIDsAddedToSeries,
episodeIDsRemovingSeries,
seriesID,
}) => {
// get the episodes that need to have the series updated
const {docs: episodesToUpdate} = await req.payload.find({
collection: 'episodes',
pagination: false,
depth: 0,
req,
where: {
id: {
in: episodeIDsAddedToSeries.concat(episodeIDsRemovingSeries),
}
}
})
const promises: Promise<any>[] = []
episodesToUpdate.forEach((episode) => {
let series = episode.series || []
if (episodeIDsAddedToSeries.includes(episode.id)) {
series = series.concat(seriesID)
} else {
series = series.filter((id) => id !== seriesID)
}
promises.push(req.payload.update({
req,
collection: 'episodes',
id: episode.id,
context,
data: {
series,
}
}))
})
await Promise.all(promises)
}
export const updateSeriesEpisodes = async ({
req,
context,
seriesIDsAddingEpisode,
seriesIDsRemovingEpisode,
episodeID,
}) => {
// get the episodes that need to have the series updated
const {docs: seriesToUpdate} = await req.payload.find({
collection: 'series',
pagination: false,
depth: 0,
req,
where: {
id: {
in: seriesIDsAddingEpisode.concat(seriesIDsRemovingEpisode),
}
}
})
const promises: Promise<any>[] = []
seriesToUpdate.forEach((series) => {
let episodes
if (seriesIDsAddingEpisode.includes(series.id)) {
episodes = series.episodes.concat({episode: episodeID})
} else {
episodes = series.episodes.filter(({ episode }) => episode !== episodeID)
}
promises.push(req.payload.update({
req,
collection: 'series',
id: series.id,
context,
data: {
episodes,
}
}))
})
await Promise.all(promises)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment