Recipe Manager
Permissions and immutable revision history
Current names and historical names serve different purposes. Uniqueness applies only to latest revisions, allowing a renamed recipe's old name to be reused. Rollback appends historical content as a new revision; it never deletes the intervening audit trail.
std::map<std::string,std::vector<Recipe>> revisions;
// latest = revisions.at(id).back(); version numbers are one based.Copy ingredient and step vectors into each revision, then return value copies from get/history. Sharing mutable vectors between versions would rewrite history after a later edit. Read and write permissions are independent: write permission does not imply read permission. Validate names before permission checks and consume recipe IDs only after acceptance. Rollback must recheck current-name collisions because another recipe may now own the historical name. Ingredient search uses current revisions, membership without duplicate hits, and ingredient count including duplicates as a sorting key.
C++ contract conventions: numeric values use signed Int (long long); all inputs and intermediate arithmetic are within its representable range. Opt<T> means std::optional<T> and absence is std::nullopt; Vec<T> means std::vector<T>. Invalid documented arguments throw std::invalid_argument. Inputs passed by const reference remain caller owned. Returned containers and records own their contents. Calls use the declared C++ parameter types. Implicit conversions performed by the caller are outside the runtime validation contract. Unless a lesson states otherwise, string ordering is lexicographic over Unicode code points (Text) or UTF-8 identifiers (std::string).
Put it into practice
Implement the exercise in a full-screen workspace, then run its tests.