Last active
September 1, 2024 18:52
-
-
Save DanRibbens/438a64157553c5d954e3da3d579f0808 to your computer and use it in GitHub Desktop.
Payload Many to Many Relationships example
This file contains hidden or 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
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, | |
}) | |
}] | |
} | |
}, | |
] | |
} |
This file contains hidden or 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
query episodesBySeries { | |
Episodes(where:{series: {equals: "65fc56c345ba752915613d5f"}}) { | |
docs { | |
id | |
title | |
} | |
} | |
} | |
query seriesByEpisode { | |
allSeries(where: { episodes__episode: { equals: "65fc545c1b3f4ed66e2fb4b2"}}) { | |
docs { | |
id | |
episodes { | |
episode { | |
title | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or 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
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', | |
}, | |
], | |
}], | |
}, |
This file contains hidden or 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
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) | |
} |
This file contains hidden or 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
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