SoulCycle has decided to move its published legal documents from a variety of duplicated locations in multiple codebases and DBs to a single source within Contentful, our CMS as a service.
One of the primary requirements around legal documents, specifically Terms & Conditions, is that they should be versioned to allow for major changes required by the legal department, and for riders' acceptance of these versions to be tracked over time.
Prior to migrating to Contentful this tracking of riders' acceptance was easily accomplished because the backing SQL database was a source of both the content of the legal documents and also a specific table for tracking riders' acceptance of them.
Within contentful we have developed a content model that supports versioning and the ability to lookup a previous version of a given document using Contentful's Content Management API (CMA). In order to accomplish this the version identifier must reside as a localized field on the entry that also holds the body of the legal document. It cannot be simply a link (relationship), otherwise Contentful's API would always return the latest version of the document, not the one that corresponded in time with the version identifier.
This means that within the content model in Contentful version identifiers will no longer be unique across all locales, but only within a locale. E.g. version id 21
may exist for both GB
and US
locales. However they are of course guaranteed to be unique per locale/country.
Given the above, our list of requirements looks like:
- Contentful represents a locale-scoped version id as an Int
- SCS is able to refererence this a locale-scoped version id that resides in Contentful
- Existing riders who have agreed to the current versions of T&C for each country/locale do not need to re-agree to them
- Apps that enable agreeing to the T&Cs as well as ensuring the rider has agreed to the most recent versions (e.g. when the rider makes a new purchase for rides) continue to be able to do so via the REST API, without any changes to the API or client code.
The plan to move forward while respecting these requirements is the following:
- Because the existing db table that tracks riders' acceptance of an agreement version employs a constrained foreign key refernence to the (now legacy)
legal_agreements_v2
table, we will creata a new table wich will represent relationship to a localized version in contentful. - A PR will be created to provide a DB migration to create this new database table which will represent a localized version with the following columns:
country_id (INT)
referencingcountries.id
in thecountries
tableversion_number (SHORTINT)
- A second PR will contain a script that migrates all users who have accepted the latest T&C for a given country/locale to the new table so that they will never be prompted to re-agree to a version they have already agreed to.
- A third PR will contain new business logic (behind a feature flag) that looks up the current active version in Contentful, compares it with what the rider has agreed to in the new table (if there is no record, the rider is deemed not to have agreed to the latest). New acceptances of T&C are of course written to the new table.
- A follow up PR will of course remove the feature flag and the related codepaths that are not behind it.
Tracking riders' acceptance of legal agreements is of course but one aspect of the migration to contentful. There are two parallel tracks of work that are onging but not coupled to this plan and therefore not described in detail here. They are:
- The generation of PDFs containing the most recently pubished T&C and their attachment to emails in SCS. The generation will occur on the fly in a GCP cloud function that is initiated via a Contentful web hook. This function stores the PDF in bucket at a given URL. Attaching the pdf to emails simply pulls the file down from the bucket. The country is encoded in the file name by convention.
- Rendering of the most recently published T&Cs in other apps. Each app will be updated to pull the data from Contentful and render as needed.
This all looks good to me!