diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..c149059af --- /dev/null +++ b/.pylintrc @@ -0,0 +1,588 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=pydantic.* + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes +w54 +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/.vscode/settings.json b/.vscode/settings.json index b74ae169c..deba22f87 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "python.formatting.provider": "black", - "python.pythonPath": "venv/bin/python", + "python.pythonPath": "/home/hayden/projects/mealie/.venv/bin/python3", "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.autoComplete.extraPaths": ["mealie", "mealie/mealie"], @@ -8,8 +8,9 @@ "python.testing.unittestEnabled": false, "python.testing.nosetestsEnabled": false, - "python.testing.cwd": "./mealie/tests", "python.testing.pytestEnabled": true, "cSpell.enableFiletypes": ["!javascript", "!python"], - "python.testing.pytestArgs": ["mealie/test/"] + "python.testing.pytestArgs": [ + "mealie" + ] } diff --git a/mealie/app.py b/mealie/app.py index 08b480db5..bc93485f6 100644 --- a/mealie/app.py +++ b/mealie/app.py @@ -2,8 +2,7 @@ import uvicorn from fastapi import FastAPI from fastapi.staticfiles import StaticFiles -import startup -from global_scheduler import start_scheduler +import utils.startup as startup from routes import ( backup_routes, meal_routes, @@ -13,8 +12,8 @@ from routes import ( static_routes, user_routes, ) -from services.settings_services import Colors, SiteTheme from settings import PORT, PRODUCTION, WEB_PATH, docs_url, redoc_url +from utils.api_docs import generate_api_docs from utils.logger import logger startup.pre_start() @@ -49,13 +48,9 @@ def invalid_api(): app.include_router(static_routes.router) - - - - # Generate API Documentation if not PRODUCTION: - startup.generate_api_docs(app) + generate_api_docs(app) if __name__ == "__main__": logger.info("-----SYSTEM STARTUP-----") diff --git a/mealie/data/db/db.json b/mealie/data/db/db.json new file mode 100644 index 000000000..2ef0c7572 --- /dev/null +++ b/mealie/data/db/db.json @@ -0,0 +1 @@ +{"recipes": {"1": {"name": "One Minute Muffin", "description": "This flax muffin is quick, vertsatile (just mix in other goodies), and only makes one at a time, so you've got no huge tray to tempt you. Custom muffins every morning!", "image": "one-minute-muffin.jpg", "recipeYield": "", "recipeIngredient": ["1\u20444 cup flax seed meal", "1\u20442 teaspoon baking powder", "1 teaspoon cinnamon", "1 egg", "1 teaspoon oil", "sugar to taste (or honey, stevia)"], "recipeInstructions": [{"text": "Mix all ingredients in a coffee mug."}, {"text": "Micorowave for one minute on high."}], "totalTime": "None", "slug": "one-minute-muffin", "categories": [], "tags": ["breakfast"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "2": {"name": "Banana Bread", "description": "From Angie's mom", "image": "banana-bread.jpg", "recipeYield": "", "recipeIngredient": ["4 bananas", "1/2 cup butter", "1/2 cup sugar", "2 eggs", "2 cups flour", "1/2 tsp baking soda", "1 tsp baking powder", "pinch salt", "1/4 cup nuts (we like pecans)"], "recipeInstructions": [{"text": "Beat the eggs, then cream with the butter and sugar"}, {"text": "Mix in bananas, then flour, baking soda/powder, salt, and nuts"}, {"text": "Add to greased and floured pan"}, {"text": "Bake until brown/cracked, toothpick comes out clean"}], "totalTime": "None", "slug": "banana-bread", "categories": [], "tags": ["breakfast", " baking"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "3": {"name": "Pasta With Mushrooms and Cashew Cream", "description": "Might you mistake this vegan cream for actual alfredo sauce? Maybe. Maybe not. It\u2019s delicious either way.", "image": "pasta-with-mushrooms-and-cashew-cream.jpg", "recipeYield": "4 Servings", "recipeIngredient": ["1 cup cashews", "4 Tbsp. (or\u00a0 more) extra-virgin olive oil, divided", "6 oz. maitake mushrooms, torn into\u00a0 bite-size pieces", "6 oz. shiitake mushrooms, stems removed, torn into bite-size pieces", "Kosher salt", "1 medium leek, white and pale green parts only, halved lengthwise, thinly sliced crosswise", "2 medium shallots, finely chopped", "2 garlic cloves, thinly sliced", "12 oz. spaghetti or other long pasta", "2 Tbsp. nutritional yeast", "2 Tbsp. finely chopped parsley", "\u00bd lemon", "Freshly ground black pepper"], "recipeInstructions": [{"text": "Preheat oven to 350\u00b0. Toast cashews on a rimmed baking sheet, tossing halfway through, until golden brown, 7\u20139 minutes. Let cool."}, {"text": "Heat 2 Tbsp. oil in a large Dutch oven or other heavy pot over medium-high. Arrange half of mushrooms in a single layer in pot and cook, undisturbed, until edges are brown and starting to crisp, about 3 minutes. Give mushrooms a good toss, then continue to cook, tossing occasionally, until all sides are brown and crisp, about 5 minutes more. Using a slotted spoon, transfer mushrooms to a plate; season with salt. Repeat with remaining 2 Tbsp. oil, remaining mushrooms, and more salt."}, {"text": "Reduce heat to medium-low and return all of the mushrooms to same pot. Add leeks, shallots, and garlic and cook, stirring often and adding another 1 Tbsp. oil if pan looks dry, until leeks and shallots are translucent and softened, about 4 minutes. Remove from heat."}, {"text": "Meanwhile, cook pasta in a large pot of boiling salted water, stirring occasionally, until very al dente, about\u00a0 2 minutes less than package directions (the pasta will finish cooking in the pan). Drain pasta, reserving \u00be cup pasta cooking liquid."}, {"text": "Blend cashews, nutritional yeast, and pasta cooking liquid in a blender until very creamy. Set cashew cream aside."}, {"text": "Return drained pasta to pot with mushroom mixture and add reserved cashew cream. Place over medium heat and cook, stirring well to coat, until pasta is al dente, about 3 minutes. Remove from heat. Add parsley and squeeze in juice from lemon half. Toss well to incorporate."}, {"text": "Divide pasta evenly among shallow bowls or plates and top with several grinds of pepper."}], "totalTime": "None", "slug": "pasta-with-mushrooms-and-cashew-cream", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/pasta-with-mushrooms-and-cashew-cream", "extras": {}}, "4": {"name": "New York Strip", "description": "Cooking a steak in a water bath is the ultimate, both in terms of flavor and laziness. Dial in a precise temperature, then go take a nap.", "image": "new-york-strip.jpg", "recipeYield": "", "recipeIngredient": ["NY Strip Steak(s)", "salt to taste"], "recipeInstructions": [{"text": "Bag the steak(s) and drop into 140\u00b0 sous vide bath for 2 hours (or consult this handy [ChefSteps chart](https://s3.amazonaws.com/chefsteps/static/ChefSteps-SousVideReference.pdf))"}, {"text": "Pull from the bag, pat dry"}, {"text": "Sear in a scorching hot pan (butter + herb optional)"}, {"text": "Slice on a bias and sprinkle with sea salt"}], "totalTime": "None", "slug": "new-york-strip", "categories": [], "tags": ["mains", " meat"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "5": {"name": "Detroit-Style Pepperoni Pizza", "description": "So you want to make pizza, but it\u2019s your first time. This Detroit-style pizza from Bryan Ford, which uses a half-batch of his Master Dough, is perfect for beginners because you don\u2019t have to shape the dough or wrangle a hot pizza stone. If you don\u2019t have a Detroit pizza pan (its deep sides and black anodized aluminum help achieve a crust that\u2019s fluffy on the inside but crisp all around), that\u2019s okay: Use a 13x9\" baking pan or two 8x8\" baking pans.", "image": "detroit-style-pepperoni-pizza.jpg", "recipeYield": "Makes 1 large or 2 small", "recipeIngredient": ["Extra-virgin olive oil (for pan)", "Bread flour (for surface)", "\u00bd batch (360 g) Master Bread Dough", "8 oz. low-moisture mozzarella or brick cheese", "\u00be cup store-bought marinara sauce", "4 oz. pepperoni, sliced", "\u00bd tsp. dried oregano", "\u00bd cup (lightly packed) basil leaves"], "recipeInstructions": [{"@type": "HowToStep", "text": "To make this recipe, start by preparing a half-batch of the Master Bread Dough."}, {"@type": "HowToStep", "text": "Lightly brush Detroit-style pizza pan, 13x9\" baking pan, or two 8x8\" pans with extra-virgin olive oil. Lightly flour work surface with bread flour and turn \u00bd batch (360 g) Master Bread Dough out onto surface. If using a Detroit-style pizza pan or a 13x9\" pan, pull edges of dough into the center, then turn over and hold opposite sides of the dough with your hands so the sides of your pinkies are touching the surface and rotate dough, gently working it into a round as you go. If using two 8x8\" pans, divide dough in half and form using the same method as above. Gently transfer dough to prepared pan. Rub dough with oil and cover with a kitchen towel. Let rise in a warm, draft-free spot until dough expands a bit and is smooth and more pliable, about 1 hour."}, {"@type": "HowToStep", "text": "Meanwhile, cut 8 oz. low-moisture mozzarella or brick cheese into small cubes. You should have 2 heaping cups."}, {"@type": "HowToStep", "text": "Place a rack in middle of oven; preheat to 450\u00b0. Dimple dough with your fingers, gently pushing out to edges of pan. Scatter cheese evenly over dough all the way to the edges. Spoon \u00be cup store-bought marinara sauce over pizza in three even rows. Next, distribute 4 oz. pepperoni, sliced, evenly over sauce. Sprinkle with \u00bd tsp. dried oregano."}, {"@type": "HowToStep", "text": "Bake pizza until crust is deep golden brown, 35\u201340 minutes. (You can also check doneness with an instant-read thermometer. It should register 190\u00b0 when inserted into a thick part of the crust.) Remove from oven and top with \u00bd cup (lightly packed) basil leaves. Let cool in pan 5 minutes before slicing."}], "totalTime": "None", "slug": "detroit-style-pepperoni-pizza", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 3, "orgURL": "https://www.bonappetit.com/recipe/detroit-style-pepperoni-pizza", "extras": {}}, "6": {"name": "Crockpot Buffalo Chicken", "description": "{'This is a party favorite, perfect for a Super Bowl or a game night. It takes a long time, but like most slow cooker recipes, is easy as hell. Bonus': 'guests love it.'}", "image": "crockpot-buffalo-chicken.jpg", "recipeYield": "", "recipeIngredient": ["6-8 chicken breasts", "1 packet ranch mix", "1 bottle Frank's Red Hot", "1 stick of butter"], "recipeInstructions": [{"text": "Add all the above into a crockpot, cook on low for 6-8 hours"}, {"text": "Shred chicken when soft"}, {"text": "Serve with pickles and Hawaiian rolls"}], "totalTime": "None", "slug": "crockpot-buffalo-chicken", "categories": [], "tags": ["mains", " grill", " party"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "7": {"name": "Buttery Kimchi Chicken", "description": "This spicy and sweet, slightly nutty, and creamy stew from developer Sunny Lee is worth the tending (and time) it requires over the stove.", "image": "buttery-kimchi-chicken.jpg", "recipeYield": "6-8 servings", "recipeIngredient": ["1 cup plain whole-milk Greek yogurt", "4 garlic cloves, finely chopped", "1 2\" piece ginger, peeled, finely chopped", "3 Tbsp. gochugaru (coarse Korean red pepper powder)", "1 Tbsp. fish sauce", "1 Tbsp. Diamond Crystal or 1\u00be tsp. Morton kosher salt", "1 tsp. freshly ground black pepper", "2 lb. skinless, boneless chicken thighs", "\u00bd cup dried soybeans", "2 Tbsp. vegetable oil", "1 onion, sliced \u00bd\" thick", "2 cups drained Napa cabbage kimchi, cut into 2\" pieces", "2 Tbsp. plus 1\u00bd tsp. tomato paste", "1\u00bd cups low-sodium chicken broth", "4 Tbsp. unsalted butter", "Cooked white rice (for serving)", "3 scallions, thinly sliced", "1 red chile (such as Fresno), thinly sliced", "Pickles (for serving; optional)"], "recipeInstructions": [{"text": "Mix yogurt, garlic, ginger, gochugaru, fish sauce, salt, and pepper in a medium bowl. Cut chicken thighs crosswise into thirds and add to bowl. Massage yogurt mixture into chicken until well coated. Cover and chill at least 1 hour and up to 3 hours."}, {"text": "Meanwhile, place soybeans in a medium saucepan and pour in 2 cups cold water; bring to a boil. Immediately drain soybeans and rinse under cold running water. Return to pot, pour in 1 cup cold water, and bring to a boil again. Immediately remove from heat and let cool slightly. Transfer soybeans and water to a blender and blend until a thick pur\u00e9e forms. (It\u2019s okay if the soybeans are a bit chunky still; they\u2019ll soften in the braise.) Set aside."}, {"text": "Heat oil in a large Dutch oven over medium-high. Working in 2 batches, remove chicken from marinade, letting any marinade that wants to cling stay on, and cook, stirring occasionally, until browned all over, 5\u20137 minutes per batch. Using a slotted spoon, transfer chicken to a plate as you go, leaving all the crunchy bits and oil in pot. Set any remaining marinade aside."}, {"text": "Reduce heat to medium-high. Combine onion, kimchi, tomato paste, and reserved marinade in same pot and cook, stirring and scraping up browned bits constantly to prevent burning, until onion is softened and tomato paste darkens slightly and begins to stick to bottom of pot, about 10 minutes. Return chicken, along with any juices collected on plate, to pot and stir in broth, butter, and reserved soybean pur\u00e9e. Bring to a simmer and cook until chicken is just cooked through, 10\u201315 minutes."}, {"text": "Divide rice among bowls and ladle chicken mixture over. Top with scallions and chile. Serve with pickles alongside if desired."}], "totalTime": "None", "slug": "buttery-kimchi-chicken", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/buttery-kimchi-chicken", "extras": {}}, "8": {"name": "Crispy Rice With Ginger-Citrus Celery Salad", "description": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle! ", "image": "crispy-rice-with-ginger-citrus-celery-salad.jpg", "recipeYield": "4 servings", "recipeIngredient": ["1 2\" piece ginger, peeled, finely grated", "1 small garlic clove, finely grated", "Juice of 1 orange", "2 tbsp. vegetable oil", "1Tbsp. coconut aminos or low-sodium soy sauce", "1 Tbsp. fresh lemon juice", "\u00bc tsp. toasted sesame oil", "Kosher salt", "1 medium head of broccoli", "6 Tbsp. (or more) vegetable oil, divided", "Kosher salt", "2 cups chilled cooked brown rice", "4 large eggs", "3 celery stalks, thinly sliced on a steep diagonal", "\u00bd cup cilantro leaves with tender stems", "\u00bd cup mint leaves", "Crushed red pepper flakes (for serving)"], "recipeInstructions": [{"text": "Whisk ginger, garlic, orange juice, vegetable oil, coconut aminos, lemon juice, and sesame oil in a small bowl; season with salt and set aside."}, {"text": "Trim about \u00bd\" from woody end of broccoli stem. Peel tough outer layer from stem. Cut florets from stems and thinly slice stems about \u00bd\" thick. Break florets apart with your hands into 1\"\u20131\u00bd\" pieces."}, {"text": "Heat 2 Tbsp. oil in a large nonstick skillet over medium. Working in 2 batches if needed, arrange broccoli in a single layer and cook, tossing occasionally, until broccoli is bright green and lightly charred around the edges, about\u00a03 minutes. Transfer to a large plate."}, {"text": "Pour 2 Tbsp. oil into same pan and heat over medium-high. Once you see the first wisp of smoke, add rice and season lightly with salt. Using a spatula or spoon, press rice evenly into pan like a pancake. Rice will begin to crackle, but don\u2019t fuss with it. When the crackling has died down almost completely, about\u00a03 minutes, break rice into large pieces and turn over."}, {"text": "Add broccoli back to pan and give everything a toss to combine. Cook, tossing occasionally and adding another\u00a01 Tbsp. oil if pan looks dry, until broccoli is tender and rice is warmed through and very crisp, about 5 minutes. Transfer mixture to a platter or divide among plates and set aside."}, {"text": "Wipe out skillet; heat remaining\u00a02 Tbsp. oil over medium-high. Crack eggs into skillet; season with salt. Oil should bubble around eggs right away. Cook, rotating skillet occasionally, until whites are golden brown and crisp at the edges and set around the yolk (which should be runny), about 2 minutes."}, {"text": "Toss celery, cilantro, and mint with\u00a03 Tbsp. reserved dressing and a pinch of salt in a medium bowl to combine."}, {"text": "Scatter celery salad over fried rice; top with fried eggs and sprinkle with red pepper flakes. Serve extra dressing alongside."}], "totalTime": "None", "slug": "crispy-rice-with-ginger-citrus-celery-salad", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad", "extras": {}}, "9": {"name": "Crispy Carrots", "description": "This is another recipe we saw on Jamie Oliver's TV show, and we can't stop making it. It's perfect for parties, and pairs well with any rich dip (we've been doing greek yogurt topped with hot sauce and tahini). We've also rolled things other than carrots, changed the flavors, made news dips\u2014 go wild!", "image": "crispy-carrots.jpg", "recipeYield": "", "recipeIngredient": ["1 lb carrot sticks (or baby carrots)", "2 oranges (or any citrus)", "2 tbsp butter", "1 package phyllo dough", "1/2 cup honey", "sesame seeds to top", "vegetable oil"], "recipeInstructions": [{"text": "Start by parboiling your carrots in 2 cups of water (means party boil)."}, {"text": "Zest and juice the oranges."}, {"text": "When the carrots are jussst soft, drain the water. Add the butter, the zest, and the orange juice. Simmer until thick and declious. It's fine (and good) if the carrots are still a bit undercooked (we're going to bake them next)."}, {"text": "Unroll the phyllo dough and cut it into three equal sections. With a brush, oil the top of each sheet."}, {"text": "Roll a carrot with a single section of phyllow dough, place on a baking sheet. Repeat and repeat and repeat."}, {"text": "When a full baking sheet is ready, brush the top of each roll with a little oil. Don't worry about placing them too far about, they won't raise much (at all)."}, {"text": "Bake according to your phyllo dough box."}, {"text": "With 5 minutes left, brush the top of each roll with honey, then sprinkle with sesame seeds."}], "totalTime": "None", "slug": "crispy-carrots", "categories": [], "tags": ["veg", " party"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "10": {"name": "Downtown Marinade", "description": "This is my go-to marinade for everything from steaks to shrimp to fajitas to pork. Seriously, if I'm cooking or grilling meat, there's a good chance this is what it's soaking in. It's perfect for \"street meat\" aka cart or truck, like tacos and wraps and kebabs and skewers and everything in between. Start with this base, add spice or herb as desired.", "image": "downtown-marinade.jpg", "recipeYield": "", "recipeIngredient": ["1 cup Italian dressing", "1/4 cup soy sauce", "1/4 cup chili paste (like [sambal oelek](https://amzn.to/2NuqquF))", "1/4 sugar", "dash hot sauce (like Texas Champagne or Yellowbird)"], "recipeInstructions": [{"text": "Mix everythiing in a large zip bag (or bowl)"}, {"text": "Toss meat and let sit (from 1-36hr)"}], "totalTime": "None", "slug": "downtown-marinade", "categories": [], "tags": ["party", " meat"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "11": {"name": "Salt & Vinegar Potatoes", "description": "This recipe [popped up](https://www.nigella.com/recipes/salt-and-vinegar-potatoes) via Google auto suggestion, probably because I love chips, and it's a super pairing for a nice roast or bird. Adjust your amount of potatoes and oil/butter to your serving size and taste.", "image": "salt-vinegar-potatoes.jpg", "recipeYield": "", "recipeIngredient": ["small potatoes", "splash of oil/butter", "splash of vinegar", "dash of salt"], "recipeInstructions": [{"text": "Start by boiling or steaming your potatoes. When they are approaching fork tender, pull them out and smash each one. Don't mash them, just crush each potato until it just starts to crack."}, {"text": "In a baking pan, add a bit of butter and olive oil (to taste and/or diet) and bake at 400\u00b0 for a few minutes or until it's nice and hot. Toss the potatoes in the hot oil."}, {"text": "Bake and then broil at 400\u00b0 until the potatoes are brown and then skins are crisp."}, {"text": "Toss in a splash of vinegar (I like plain white, some use apple cider or red wine vinegar) and a pinch of good sea salt."}], "totalTime": "None", "slug": "salt-vinegar-potatoes", "categories": [], "tags": ["sides", " veg"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "12": {"name": "Mushroom Risotto", "description": "Risotto is one of those recipes that sound tough but really isn't that tricky (but is totally tasty). This recipe is a mash between a [Jamie Oliver](https://www.jamieoliver.com/recipes/rice-recipes/grilled-mushroom-risotto/) method and a [Tasty 101 video](https://tasty.co/recipe/mushroom-risotto). Jamie asked for dried mushrooms (proved to be hard to find and expensive here) and Tasty didn't have enough mushroom punch (so we added back the roasted topper). The best of both worlds, and easier than I've previously imagined!", "image": "mushroom-risotto.jpg", "recipeYield": "", "recipeIngredient": ["10oz shitake mushrooms (we usually buy two 5oz grocery packages)", "1 cup arborio rice", "2 cups chicken stock", "4 tbsp butter (halved)", "1 small onion, diced", "6 cloves garlic", "1.5 cups white wine", "1 cup beer (drink the rest)", "half a lemon", "1 cup fresh grated parmesan cheese, plus more for serving", "sprig of parsley (or your favorite herb)", "salt", "pepper"], "recipeInstructions": [{"text": "Add the chicken stock, 1 cup white wine, and 1 cup beer to a stock pock and bring to a simmer (4 cups total). Reduce heat or set aside."}, {"text": "Add 2 tbsp butter, garlic, and onions to a deep pan and cook until translucent (a couple minutes). Add one package (or half) of your mushrooms and cook down. Salt and pepper to taste (small dash of salt, hearty crack of pepper for me, please)."}, {"text": "Add the rice into the pan and toss to coat. Toast for a few minutes."}, {"text": "Add the remaining white wine and juice from half a lemon to deglaze the pan, scraping up all the good bits."}, {"text": "Add a large ladle (or two) of the hot stock mixture into the rice pan. Here's where we get patient. Cook the stock down (near dry) before adding another ladle. Repeat."}, {"text": "Continue to add a ladle at a time, letting it fully absord while stirring, until the stock is gone or the rice is tender. Fair game to taste the rice a few times in search of the perfect tenderness."}, {"text": "Meanwhile, let's roast our other pack of fresh mushrooms. Get the remaining half on a sheet pan and under a 450\u00b0 broiler. Keep an eye on them but cook til crispy and brown on the edges."}, {"text": "When the rice is tender and the mushrooms in the oven are crisp, stir your parmesan and remaining 2tbps butter into the rice. Plate your risotto and top with our roasted mushrooms, our herb, more parmesan, and a crack of black pepper."}], "totalTime": "None", "slug": "mushroom-risotto", "categories": [], "tags": ["mains", " veg"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "13": {"name": "Nilla Wafer French Toast", "description": "From a weird Sunday morning when I realized we didn't have bread. Solid hitter. Served with [Treehive](https://amzn.to/2QBpGCH) Syrup, my new favorite breakfast obsession (it's mixed with honey, vanilla, and cinnamon, oof).", "image": "nilla-wafer-french-toast.jpg", "recipeYield": "", "recipeIngredient": ["4 eggs", "1/4 cup heavy cream", "2 tsp vanilla", "sprinkle of sugar", "4 cups Nilla Wafers Minis"], "recipeInstructions": [{"text": "Beat the eggs, heavy cream, vanilla, and sugar."}, {"text": "Arrange the nillas in a few flat layers in a baking dish."}, {"text": "Pour egg mixture over nillas. Let soak while oven preheats to 350\u00b0. Top with shake of cinnamon sugar."}, {"text": "Bake until egg has set (it'll fluff up a bit)"}], "totalTime": "None", "slug": "nilla-wafer-french-toast", "categories": [], "tags": ["breakfast", " baking"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "14": {"name": "Roasted Okra", "description": "I love me some okra, but prep is usually enough to avoid making it. This simple dish requires only a single cut per pod and a dash of seasoning- that's it.", "image": "roasted-okra.jpg", "recipeYield": "", "recipeIngredient": ["1 bunch of okra", "salt, pepper, etc to taste"], "recipeInstructions": [{"text": "Wash the okra dang good"}, {"text": "Slice in half lengthwise"}, {"text": "Toss okra and seasoning on baking sheet (salt + pepper is great, I also really like Simply Asia spicy hibachi seasoing I found at HEB for like $2)"}, {"text": "Roast under the broiler for 10-15 mins. Keep an eye on it and pull it when the tips start to blacken."}], "totalTime": "None", "slug": "roasted-okra", "categories": [], "tags": ["sides", " vegetables"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "15": {"name": "Mississippi Pot Roast", "description": "This is the recipe that rocked reddit, coming our way via [Today](https://www.today.com/today/amp/tdna199419) (believe it or not). It takes just minutes with almost no prep (use a slow cooker liner for no cleanup), and the leftovers can be used for sandwiches or nachos or eggs. If you like a thicker gravy, combine the dripping with flour/starch, or just enjoy the thinner jus.", "image": "mississippi-pot-roast.jpg", "recipeYield": "", "recipeIngredient": ["3-4 lbs chuck roast", "1 packet ranch mix", "1 packet au jus gravy mix", "1 stick butter", "1 jar pepperoncinis"], "recipeInstructions": [{"text": "In your slow cooker, add the roast and top with both packet mixes, a stick of butter (sliced into a few chunks), and a jar of pepperoncini peppers (the more the better, drained)"}, {"text": "Cook for 8 hours on low"}, {"text": "Serve with salt & vinegar potatoes"}], "totalTime": "None", "slug": "mississippi-pot-roast", "categories": [], "tags": ["mains", " crock pot"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "16": {"name": "Tamarind Chicken Thighs With Collard Greens Salad", "description": "Tamarind concentrate gives this chicken its sticky, glossy quality, not to mention its sweet-and-sour flavor.", "image": "tamarind-chicken-thighs-with-collard-greens-salad.jpg", "recipeYield": "4 servings", "recipeIngredient": ["8 skin-on, bone-in chicken thighs\u00a0 (about 2\u00bd lb.), patted dry", "Kosher salt", "2 serrano chiles, coarsely chopped", "1 2\" piece ginger, peeled,\u00a0 chopped", "3 garlic cloves", "\u2153 cup tamarind concentrate", "3 Tbsp. coconut aminos", "2 Tbsp. agave nectar", "2 tsp. ground cinnamon", "1 tsp. ground nutmeg", "\u00bd cup plus 3 Tbsp. fresh orange juice (from about 2 oranges)", "3 Tbsp. extra-virgin olive oil, divided", "2 medium Japanese sweet potatoes or other sweet potatoes (about\u00a0 1 lb.), scrubbed, sliced into 1/8\"\u20131/4\"-thick rounds", "Half a bunch collard greens (about 6 oz.), stems removed, leaves cut or torn into bite-size pieces", "Freshly ground black pepper"], "recipeInstructions": [{"text": "Preheat oven to 375\u00b0. Place chicken in a large bowl and season all over with salt; set aside."}, {"text": "Blend chiles, ginger, garlic, tamarind concentrate, coconut aminos, agave, cinnamon, nutmeg, and \u00bd cup orange juice in a blender until smooth. Transfer to a small saucepan and place over medium heat. Cook, stirring often, until glaze is sticky and easily coats a spoon, 6\u20138 minutes. Let cool. Pour glaze over chicken and toss to coat."}, {"text": "Pour 2 Tbsp. oil into a large skillet and, using tongs, arrange chicken, skin side down, in pan, leaving excess glaze behind in bowl. Cook, undisturbed, over medium heat, until skin is browned and crisp, 10\u201312 minutes. Transfer chicken to a plate."}, {"text": "Let skillet cool 5 minutes, then arrange sweet potatoes in an even, slightly overlapping layer in pan.\u00a0Season with salt and add 2 Tbsp water. Place chicken, skin side up, on top. Transfer pan to oven and roast until chicken is cooked through and juices run clear when flesh is pierced with the tip of a small knife, 10\u201312 minutes. Let rest in pan 10 minutes."}, {"text": "While the chicken is resting, toss collard greens, remaining 3 Tbsp. orange juice, and remaining 1 Tbsp. oil in a large bowl to combine; season with a pinch of salt and a few grinds of pepper. Lightly massage greens with your hands to soften slightly."}, {"text": "Serve chicken and sweet potatoes with collard greens salad alongside."}], "totalTime": "None", "slug": "tamarind-chicken-thighs-with-collard-greens-salad", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/tamarind-chicken-thighs-with-collard-greens-salad", "extras": {}}, "17": {"name": "Cauliflower Cacciatore", "description": "We first discovered this dish at [Arlo Grey](https://www.thelinehotel.com/austin/food-drink/) in Downtown Austin, and since, it's taken on a life of its own. We've cooked it a number of times and it's slowly evolved (mainly to become easier to cook, if you can believe it). It's one of our favorite vegetarian dishes and can easily swing vegan (just replace the dairy with something like hummus).\nIt's vegetables that eat like a full-on meat lasagna. Dang.", "image": "cauliflower-cacciatore.jpg", "recipeYield": "", "recipeIngredient": ["1 head of cauliflower", "1lb mini sweet peppers", "1 yellow onion, diced", "5oz mild goat cheese (we like Chavrie brand)", "5oz sour cream", "jar of cacciatore sauce (we like Cookwell & Company)", "1 cup red wine", "croutons (we make our own)", "fresh cilantro"], "recipeInstructions": [{"text": "Start by pickling half your peppers. Chop all your peppers, put half in a jar, cover with vinegar (and whatever you like, spices, garlic, hot sauce). We're only looking for a quick pickle here, so do this, shake it up, and get back to the rest of the recipe."}, {"text": "Next, start roasting the cauliflower, around 350\u00b0, to your desired level of doneness. Keep it firm and you can call it a \"cauliflower steak\", cook it down hard and it'll eat more like a mash. If you're making your own croutons, add some bread cubes tossed in oil/salt/pepper around the roasting pan."}, {"text": "While the cauliflower roasts, stir the goat cheese and sour cream together (and whatever you like, spices, garlic, hot sauce). If it's too thick, thin it out with a bit of olive oil."}, {"text": "Cook the onion and remaining peppers (non-pickled!) in a high heat pan with some olive oil. When softened, add your jar of cacciatore sauce and a cup of red wine. Stir and bring to a simmer, let it go until it gets a bit thicker."}, {"text": "To assemble, plate a large dollop of our cream cheese spread, place half a cauliflower head on top, cover with a scoop of cacciatore sauce, top with croutons, cilantro, and our pickled peppers."}], "totalTime": "None", "slug": "cauliflower-cacciatore", "categories": [], "tags": ["mains", " veg"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "18": {"name": "Marranitos Enfiestados", "description": "Rick Martinez\u2019s take on the popular Mexican pig-shaped cookie goes all out with a ginger-spiced dough and tons of sprinkles.", "image": "marranitos-enfiestados.jpg", "recipeYield": "", "recipeIngredient": ["1\u00bd cups (210 g) whole wheat flour", "1 Tbsp. ground cinnamon", "1 Tbsp. ground ginger", "1 tsp. ground allspice", "\u00bc tsp. ground cloves", "1 tsp. baking powder", "\u00bd tsp. baking soda", "3\u00be cups (469 g) all-purpose flour, plus more for dusting", "12 Tbsp. (1\u00bd sticks) unsalted butter, room temperature, divided", "\u00bd cup (100 g) plus 1 tsp. granulated sugar", "1\u00bc tsp. Diamond Crystal or \u00be tsp. Morton kosher salt, divided", "3 large eggs, room temperature", "2 tsp. vanilla extract, divided", "\u2153 cup light agave syrup (nectar), honey, or light corn syrup", "\u00bd cup (100 g) grated or granulated piloncillo or (packed) dark brown sugar", "\u2153 cup robust-flavored (dark) molasses", "Nonstick vegetable oil spray", "Sanding sugar or sprinkles (for decorating)", "A 4\" pig-shaped cookie cutter"], "recipeInstructions": [{"@type": "HowToStep", "text": "Whisk whole wheat flour, cinnamon, ginger, allspice, cloves, baking powder, baking soda, and 3\u00be cups (469 g) all-purpose flour in a medium bowl. Using an electric mixer on medium-high speed, beat 6 Tbsp. butter, \u00bd cup (100 g) granulated sugar, and half of salt in a large bowl, scraping down sides of bowl as needed, until light and creamy, about 3 minutes. Add 1 egg and 1 tsp. vanilla; beat to combine. Add agave and beat just until smooth. Reduce speed to low, add half of dry ingredients (if you have a scale, use it!), and beat to combine, scraping down sides of bowl as needed. Dough will be slightly sticky. Wrap in plastic; pat into a square about \u00be\" thick. Chill at least 3 hours and up to 1 day."}, {"@type": "HowToStep", "text": "Clean bowl and beaters. With mixer on medium-high speed, beat piloncillo, remaining salt, and remaining 6 Tbsp. butter, scraping down sides of bowl as needed, until light and creamy, about 3 minutes. Add 1 egg and remaining 1 tsp. vanilla; beat until combined. Add molasses and beat until smooth. Reduce speed to low; beat in remaining dry ingredients, scraping down sides of bowl as needed. Wrap in plastic; pat into a square about \u00be\" thick. Chill at least 3 hours and up to 1 day."}, {"@type": "HowToStep", "text": "Place racks in upper and lower thirds of oven; preheat to 350\u00b0. Line 3 baking sheets with parchment paper and lightly coat with nonstick spray. Cut both doughs into about \u00be\" pieces. (Don\u2019t worry about being super precise; spots will look better if pieces are different shapes and sizes.) Arrange about half of brown and white dough pieces, touching and alternating colors, in an even layer on a lightly floured piece of parchment. (Chill remaining dough while you work.) Roll out \u00bc\" thick. Punch out cookies with lightly floured cutter and transfer to prepared baking sheets, spacing \u00be\" apart. Arrange scraps in a single layer so they are touching and cover with plastic; chill 10 minutes if soft. Roll out scraps and cut out more pigs. Repeat with remaining dough and scraps."}, {"@type": "HowToStep", "text": "Beat remaining egg and remaining 1 tsp. granulated sugar in a small bowl. Using your finger, rub egg on brown spots only (egg will darken the spots) and along edges of cookies. Sprinkle sanding sugar or sprinkles along the edges of cookies, gently pressing into dough to adhere. Chill 10 minutes."}, {"@type": "HowToStep", "text": "Working in batches, bake cookies, rotating baking sheets top to bottom and front to back halfway through, until puffed and light spots are golden, 10\u201312 minutes. Let cookies cool 10 minutes on baking sheets, then transfer to a wire rack. Let cool completely.\nDo ahead: Cookies can be made 2 days ahead. Store airtight at room temperature."}], "totalTime": "None", "slug": "marranitos-enfiestados", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 4, "orgURL": "https://www.bonappetit.com/recipe/marranitos-enfiestados", "extras": {}}, "19": {"name": "Smashed Carrots", "description": "Generally, canned carrots are stinky and not worth eating. Butter and cheese changes that.", "image": "smashed-carrots.jpg", "recipeYield": "", "recipeIngredient": ["1 can carrots", "1 tbsp butter", "2 tbsp parmesan cheese", "salt & pepper to taste"], "recipeInstructions": [{"text": "Combine carrots and butter, heat through (yes, you can microwave)"}, {"text": "Mash carrots, adding parmesan when smooshy"}, {"text": "Salt and pepper how you like"}], "totalTime": "None", "slug": "smashed-carrots", "categories": [], "tags": ["sides", " vegetables"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "20": {"name": "Pizzettes", "description": "Not to be confused with wafer-thin waffled pizzelles, this rich brownie-like cookie packed with warm spices is exactly the opposite.", "image": "pizzettes.jpg", "recipeYield": "Makes about 20", "recipeIngredient": ["\u00be cup raw skin-on almonds", "1\u00bd cups (188 g) all-purpose flour", "1 tsp. baking powder", "\u00bd tsp. Diamond Crystal or \u00bc tsp. Morton kosher salt", "\u00bd cup (1 stick) unsalted butter", "\u00be tsp. ground cinnamon", "\u00bd tsp. ground cloves", "\u00bd tsp. ground nutmeg", "\u00bd cup (100 g) granulated sugar", "\u00bc cup Dutch-process cocoa powder", "1 large egg", "1 large egg yolk", "4 oz. bittersweet chocolate chips (about 1 heaping cup)", "1 tsp. finely grated lemon zest", "1 tsp. finely grated orange zest", "2 oz. bittersweet chocolate", "2 tsp. unsalted butter", "\u2154 cup (75 g) powdered sugar", "Pinch of kosher salt"], "recipeInstructions": [{"@type": "HowToStep", "text": "Place racks in upper and lower thirds of oven; preheat to 350\u00b0. Toast almonds on a rimmed baking sheet, tossing once, until slightly darkened in color and fragrant, 7\u201310 minutes. Let cool, then finely chop. Set aside. Increase oven temperature to 375\u00b0."}, {"@type": "HowToStep", "text": "Meanwhile, whisk flour, baking powder, and salt in a medium bowl. Melt butter in a medium saucepan over medium heat. Stir in cinnamon, cloves, and nutmeg. Remove from heat and let sit 5 minutes to infuse."}, {"@type": "HowToStep", "text": "Whisk granulated sugar and cocoa powder in a large bowl. Pour in spiced butter, scraping pan so you don\u2019t leave any spices behind, and whisk vigorously to combine. Add egg and egg yolk; whisk vigorously to combine. Mix in dry ingredients, then almonds, chocolate chips, lemon zest, and orange zest. Turn out onto a clean surface. Divide in half, then roll each half into a log about 1\u00bd\" wide. Flatten logs to 2\" wide and slice on a diagonal into 1\"-wide cookies. Divide between 2 parchment-lined baking sheets, spacing at least 1\" apart."}, {"@type": "HowToStep", "text": "Bake cookies, rotating baking sheets top to bottom and front to back halfway through, until firm around edges but still soft in the middle, 8\u201310 minutes. Let cool."}, {"@type": "HowToStep", "text": "Melt chocolate and butter in a small heatproof bowl set over a small saucepan of barely simmering water (do not let bowl touch water), stirring occasionally, or melt in a small microwave-safe bowl in a microwave in 20-second bursts, stirring between bursts. Add powdered sugar, salt, and 2 Tbsp. boiling water and whisk until glaze is smooth and glossy."}, {"@type": "HowToStep", "text": "Dip tops of cookies into glaze and let sit, glaze side up, on a wire rack until glaze is set, about 2 hours."}, {"@type": "HowToStep", "text": "Do ahead: Pizzettes can be made 5 days ahead. Store airtight at room temperature, or freeze up to 1 month."}], "totalTime": "None", "slug": "pizzettes", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/chocolate-pizzettes-cookies", "extras": {}}, "21": {"name": "Jalapeno Cornbread", "description": "This corny cornbread pairs perfectly with chili or a bowl of stew. Add corn to the batter and you'll never go back.", "image": "jalapeno-cornbread.jpg", "recipeYield": "", "recipeIngredient": ["1 box \"fiesta\" corn bread mix", "1 can of sweet corn", "1 cup sour cream", "pickled jalape\u00f1os to taste"], "recipeInstructions": [{"text": "Make the cornbread mix according to the box (usually needs eggs and milk)."}, {"text": "Add a full can or corn (a regular sized can) and the sour cream (1 cup)."}, {"text": "Pour into a large glass baking dish and top with jalape\u00f1os."}, {"text": "Bake a few mins longer than the box says (we've added a lot of liquid)."}], "totalTime": "None", "slug": "jalapeno-cornbread", "categories": [], "tags": ["sides", " bread", " spicy"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "22": {"name": "Chicken Salad With Citrus and Chile Oil", "description": "With plenty of protein, a double punch of acid from citrus and vinegar, and a garlicky chili oil, this salad is made for dinner.", "image": "chicken-salad-with-citrus-and-chile-oil.jpg", "recipeYield": "4 servings", "recipeIngredient": ["3 skin-on, bone-in chicken breasts\u00a0 (about 1\u00bd lb. total)", "Kosher salt, freshly ground pepper", "2 Tbsp. plus \u2153 cup extra-virgin olive oil", "3 garlic cloves, thinly sliced", "2 tsp. paprika", "1 tsp. coriander seeds, coarsely\u00a0crushed", "\u00bd tsp. crushed red pepper flakes", "2 medium endive, leaves separated", "1 large head of radicchio, leaves\u00a0 separated, torn if large", "4 satsumas or blood oranges or 3 medium oranges, peeled, sliced into rounds, seeds removed", "2 Tbsp. white wine vinegar or red wine vinegar", "2 tsp. toasted sesame seeds, lightly crushed"], "recipeInstructions": [{"text": "Preheat oven to 450\u00b0. Pat chicken breasts dry with paper towels; season on all sides with salt and pepper, then rub with 2 Tbsp. oil."}, {"text": "Heat a large ovenproof skillet over medium-high. Arrange chicken, skin side down, in pan and cook, undisturbed, until skin is deep golden brown, about 3 minutes. Turn chicken over with tongs and transfer skillet to oven. Roast chicken until cooked all the way through, 15\u201317 minutes. Transfer to a cutting board and let cool 10 minutes."}, {"text": "Meanwhile, cook garlic and remaining \u2153 cup oil in a small skillet over medium heat, stirring occasionally, until garlic is fragrant and pale golden, about 4 minutes. Immediately pour garlic oil into a small bowl and stir in paprika, coriander seeds, and red pepper flakes; season with salt."}, {"text": "Cut chicken off the bone, then slice\u00a0 \u00bd\" thick; discard bones."}, {"text": "Toss endive, radicchio, and satsumas with vinegar and half of spiced garlic oil in a large bowl to combine; season salad with salt and pepper."}, {"text": "Divide salad among plates or shallow bowls; top with chicken and drizzle with more spiced garlic oil. Sprinkle sesame seeds over."}], "totalTime": "None", "slug": "chicken-salad-with-citrus-and-chile-oil", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/chicken-salad-with-citrus-and-chile-oil", "extras": {}}, "23": {"name": "Green Chile Stew", "description": "This is a simple one, consisting of a pre-made soup mix and whatever you've got around the kitchen (or campsite). Feel free to sub/swap/switch anything in the ingredient list\u2014 anything's fair game!", "image": "green-chile-stew.jpg", "recipeYield": "", "recipeIngredient": ["1 jar of [green chile stew mix](http://amzn.to/1KYXSjo)", "1lb ground chicken", "1 red onion", "1 green onion", "1 can corn"], "recipeInstructions": [{"text": "Start onions in pan, cook down"}, {"text": "Add chicken, corn"}, {"text": "Add stew mix"}, {"text": "Serve with croutons"}], "totalTime": "None", "slug": "green-chile-stew", "categories": [], "tags": ["sides", " soups"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "24": {"name": "Broccoli Beer Cheese Soup", "description": "This recipe is inspired by one of my favorites, Gourmand's Beer Cheese Soup, which uses Shiner Bock. Feel free to use whatever you want, then go to [Gourmand's](http://lovethysandwich.com) to have the real thing.", "image": "broccoli-beer-cheese-soup.jpg", "recipeYield": "", "recipeIngredient": ["4 tablespoons butter", "1 cup diced onion", "1/2 cup shredded carrot", "1/2 cup diced celery", "1 tablespoon garlic", "1/4 cup flour", "1 quart chicken broth", "1 cup heavy cream", "10 ounces muenster cheese", "1 cup white white wine", "1 cup pale beer", "1 teaspoon Worcestershire sauce", "1/2 teaspoon hot sauce"], "recipeInstructions": [{"text": "Start with butter, onions, carrots, celery, garlic until cooked down"}, {"text": "Add flour, stir well, cook for 4-5 mins"}, {"text": "Add chicken broth, bring to a boil"}, {"text": "Add wine and reduce to a simmer"}, {"text": "Add cream, cheese, Worcestershire, and hot sauce"}, {"text": "Serve with croutons"}], "totalTime": "None", "slug": "broccoli-beer-cheese-soup", "categories": [], "tags": ["sides", " soups"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "25": {"name": "Pork Steaks", "description": "Pork is called \"the other white meat\", but this sous vide technique is so dang good I'm gonna graduate it up to \"THE white meat\".", "image": "pork-steaks.jpg", "recipeYield": "", "recipeIngredient": ["pork shoulder (or other cheap cut)", "immerison circulator (we like the [Anova](http://www.amazon.com/gp/product/B00UKPBXM4/ref=as_li_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=B00UKPBXM4&linkCode=as2&tag=repl-20&linkId=XMRXWQ35OJNCZVGE))"], "recipeInstructions": [{"text": "Sous vide the pork at 140\u00b0 for 24h"}, {"text": "Submerge bag into an ice bath to completely cool"}, {"text": "Cut and portion the cold roast into individual steaks"}, {"text": "Seal and save (fridge for 3 days, freezer for longer)"}, {"text": "To serve, reheat in bag, sear in sourching hot pan"}], "totalTime": "None", "slug": "pork-steaks", "categories": [], "tags": ["mains", " meat"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "26": {"name": "Pace Pork", "description": "This recipe comes from Cody, a childhood neighbor and friend that my brother and I group chat message about food literally every day. Hey Cody!\nConsider this a starting point. This is totally one of those \"starter meats\" that you coud add to almost any dish.", "image": "pace-pork.jpg", "recipeYield": "", "recipeIngredient": ["pork shoulder (or loin)", "1 jar of Pace Picante"], "recipeInstructions": [{"text": "Combine the pork and Pace Picante in the crockpot"}, {"text": "Cook on slow for 8hrs or so (until it shreds)"}, {"text": "Eat on buns, on tortillas, with chips or nachos, etc"}], "totalTime": "None", "slug": "pace-pork", "categories": [], "tags": ["mains", " crock pot"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "27": {"name": "One-Pot Chicken and Rice", "description": "We took loose inspiration from Hainanese chicken rice to create this weeknight-friendly dish that retains the comforting and fragrant qualities of the beloved original. Seek out the highest-quality basmati rice you can find (we recommend Daawat brand)\u2014it will make a world of difference in yielding supremely fluffy, evenly cooked rice.", "image": "one-pot-chicken-and-rice.jpg", "recipeYield": "6 servings", "recipeIngredient": ["6 small skin-on, bone-in chicken thighs (about 3 lb.)", "\u00be tsp. kosher salt, plus more", "2 large shallots, finely chopped", "8 garlic cloves, finely chopped", "1 2\" piece ginger, peeled, finely chopped", "2 cups high-quality basmati rice, rinsed", "2 whole star anise", "1 Tbsp. soy sauce", "\u00bd cup creamy peanut butter", "2 Tbsp. soy sauce", "2 Tbsp. unseasoned rice vinegar", "5 tsp. Sriracha", "1 1\" piece ginger, peeled, finely chopped", "1\u00bd tsp. honey", "1 English hothouse cucumber, halved lengthwise, thinly sliced on a diagonal", "1 cup cilantro leaves with tender stems"], "recipeInstructions": [{"@type": "HowToStep", "text": "Pat chicken dry and season all over with salt. Arrange, skin side down, in a cold, dry medium Dutch oven and set over medium heat. Cook, undisturbed, until skin is golden brown and crisp and easily releases from pot, 8\u201310 minutes. Slide thighs around to different spots (this will ensure even browning) and continue to cook until deeply browned, 3\u20135 minutes longer. Transfer chicken to a plate. Let pot cool 2 minutes."}, {"@type": "HowToStep", "text": "Set pot over medium-low heat; add shallots, garlic, and ginger and cook, stirring constantly, until very fragrant but not browned, about 2 minutes. Stir in rice and star anise and cook, stirring constantly, 1 minute. Stir in soy sauce, \u00be tsp. salt, and 3 cups water. Nestle chicken back into rice mixture in pot, arranging skin side up. Increase heat to medium and bring liquid to a simmer. Immediately cover pot, reduce heat to low, and cook 25 minutes. Remove from heat and let chicken and rice sit 10 minutes."}, {"@type": "HowToStep", "text": "While the chicken and rice are cooking, make the sauce. Whisk peanut butter, soy sauce, vinegar, Sriracha, ginger, honey, and \u00bc cup warm water in a small bowl until smooth and pourable. If sauce still looks a little thick, continuing adding water until it reaches a drizzle-able consistency."}, {"@type": "HowToStep", "text": "Remove lid from pot and fluff rice with a fork. Pluck out and discard star anise. Serve chicken and rice with peanut sauce, cucumber, and cilantro alongside."}, {"@type": "HowToStep", "text": "Do Ahead: Sauce can be made 3 days ahead. Cover and chill."}], "totalTime": "None", "slug": "one-pot-chicken-and-rice", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/one-pot-chicken-and-rice#intcid=_bon-appetit-recipe-bottom-recirc_ee10e70e-8f95-4252-8065-3acbde6d4d31_similar2-3", "extras": {}}, "28": {"name": "Coffee-Hazelnut Biscotti", "description": "Embrace biscotti as the anti-gooey-chewy cookie that can be packed with coffee, chocolate, and nuts, and dunked in amaro, bourbon, or cold milk.", "image": "coffee-hazelnut-biscotti.jpg", "recipeYield": "Makes about 22", "recipeIngredient": ["\u2154 cup blanched hazelnuts", "3 large eggs", "2 Tbsp. instant espresso powder or instant coffee powder", "2 cups (250 g) all-purpose flour", "2 tsp. baking powder", "1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt", "\u00bd cup (1 stick) unsalted butter, room temperature", "1 cup (200 g) sugar, plus more for sprinkling", "2 tsp. coffee extract or vanilla extract", "3 oz. coffee-flavored chocolate or semisweet chocolate, coarsely chopped"], "recipeInstructions": [{"@type": "HowToStep", "text": "Place racks in upper and lower thirds of oven; preheat to 350\u00b0. Toast hazelnuts on a rimmed baking sheet, tossing once, until golden brown, 8\u201310 minutes. Let cool, then very coarsely chop. Reduce oven heat to 325\u00b0."}, {"@type": "HowToStep", "text": "Separate yolk from 1 egg over a small bowl to catch egg white. Place yolk in another small bowl. Set egg white aside for brushing dough. Crack remaining 2 eggs into bowl with yolk. Add espresso powder and beat with a fork to combine and dissolve."}, {"@type": "HowToStep", "text": "Whisk flour, baking powder, and salt in a medium bowl. Using an electric mixer on medium speed, beat butter and 1 cup (200 g) sugar until light and creamy, about 2 minutes. Scrape down sides of bowl, then add egg yolk mixture and coffee extract; beat just to combine. Scrape down sides of bowl and beat until smooth. Add dry ingredients and mix on low speed to combine. Add hazelnuts and chocolate and mix just to evenly distribute. Divide dough evenly between 2 parchment-lined baking sheets. Run your hands under cold water, then shape each dough mound into a 5\" square about 1\" thick. Beat reserved egg white with a fork until foamy and brush over loaves (you won\u2019t need all of it). Sprinkle very generously with sugar."}, {"@type": "HowToStep", "text": "Bake loaves, rotating baking sheets top to bottom and front to back halfway through, until firm in the center and starting to crisp at the edges, 30\u201335 minutes. (They will spread quite a bit as they bake.) Transfer baking sheets to wire racks; let loaves cool 15 minutes."}, {"@type": "HowToStep", "text": "Working one at a time, carefully transfer loaves to a cutting board. Using a serrated knife, slice \u00bd\" thick. Arrange biscotti cut side down on baking sheets and bake, rotating baking sheets top to bottom and front to back halfway through, until dry and crisp, 30\u201335 minutes. Transfer baking sheets back to racks and let biscotti cool.\nDo ahead: Biscotti can be baked 1 week ahead. Store airtight at room temperature."}], "totalTime": "None", "slug": "coffee-hazelnut-biscotti", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 3, "orgURL": "https://www.bonappetit.com/recipe/coffee-hazlenut-biscotti", "extras": {}}, "29": {"name": "Falafel-Hummus Plate", "description": "I first ate this 2019 at a restaurant in Brussels where I was together with my sister. It's an easy, very delicious plate that can be served in many different variations.", "image": "falafel-hummus-plate.jpg", "recipeYield": "", "recipeIngredient": ["falafel", "olives", "eggplant", "carrots", "sugar", "tahini", "hummus", "sesame seeds", "olive oil", "pomegranate seeds", "parsley", "vinegar", "salt, pepper, chili flakes"], "recipeInstructions": [{"text": "prepare Hummus and Falafel dough according to recipe"}, {"text": "cut eggplant into 1cm pieces, slice carrot in half a centimeter slices."}, {"text": "saute eggplant and carrot slice together in some oil, sugar"}, {"text": "carammelize some sugar in a pan, add eggplant and carrot slices. Saut\u00e9 until soft."}, {"text": "Chop parsley and combine with pomegranate seeds and some vinegar to create a side salad"}, {"text": "form Falafel balls, touch into sesame seeds and fry until golden brown using some olive oil"}, {"text": "serve Falafel ontop of hummus aside olives, parsley salad, eggplant-carrot-mixture"}, {"text": "decorate using chili flakes and tahini"}], "totalTime": "None", "slug": "falafel-hummus-plate", "categories": [], "tags": ["vegetarian", " fast", " chickpeas"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "30": {"name": "Shrimp and Cabbage Curry", "description": "Make this shrimp and cabbage curry vegetarian by skipping the shrimp and using cubed pumpkin or squash instead.", "image": "shrimp-and-cabbage-curry.jpg", "recipeYield": "4 servings", "recipeIngredient": ["1 red bell pepper, ribs and seeds\u00a0 removed, coarsely chopped", "2 red Thai chiles or 1 small red\u00a0Fresno chile, seeds removed if\u00a0 desired, coarsely chopped", "1 lemongrass stalk, bottom third only, tough outer layers removed, finely chopped", "4 garlic cloves, smashed", "1 4\" piece ginger, peeled, finely\u00a0 grated", "2 Tbsp. smoked paprika", "2 tsp. ground coriander", "1 tsp. ground cumin", "1 tsp. ground turmeric", "Kosher salt", "3 Tbsp. (or more) virgin coconut oil", "\u00bd medium head of green cabbage, cut into 4 wedges through\u00a0 root end", "1 13.5-oz. can unsweetened\u00a0 coconut milk", "Kosher salt", "1 lb. large shrimp, shelled, deveined", "4 scallions, chopped", "2 tsp. finely grated lime zest", "2 Tbsp. fresh lime juice", "Small handful of torn tender herbs (such as cilantro, basil, and/or\u00a0 mint)", "Lime wedges (for serving)"], "recipeInstructions": [{"text": "Blend red bell pepper, chiles, lemongrass, garlic, ginger, paprika, coriander, cumin, turmeric, and a couple of big pinches of salt in a blender until a smooth paste forms."}, {"text": "Heat oil in a large pot over medium-high. Cook cabbage until deeply browned on both cut sides, about\u00a0 2 minutes per side. Transfer to a plate."}, {"text": "If pot looks dry, add another 1 Tbsp. oil. Add curry paste to pot (still over medium-high heat) and cook, stirring often, until paste is slightly darkened in color and beginning to stick to bottom of pot, about 5 minutes. Pour coconut milk and 2 cups water into pot and reduce heat to medium; season with salt. Cook, scraping up any curry paste stuck to pot until flavors come together and curry is slightly thickened, 10\u201312 minutes."}, {"text": "While the curry is cooking, coarsely chop cabbage."}, {"text": "Season shrimp with salt and add to curry. Cook, stirring, until shrimp are just cooked through, about 3 minutes. Remove pot from heat; stir in cabbage, scallions, and lime zest and juice."}, {"text": "Divide curry among bowls and top with herbs. Serve with lime wedges."}], "totalTime": "None", "slug": "shrimp-and-cabbage-curry", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/shrimp-and-cabbage-curry", "extras": {}}, "31": {"name": "Roasted Brussels Sprouts", "description": "A lot of places are doing dang good brussels lately, but none can touch [Salty Sow](http://saltysow.com/). They serve them fried, topped with pecorino and golden raisins. Yes, please.\nWe started trying to emulate it, but it's such a versatile dish, you can make it however you want. Consider this a loose guide.", "image": "roasted-brussels-sprouts.jpg", "recipeYield": "", "recipeIngredient": ["1 bunch of brussels, sliced thin", "2 tbsp olive oil", "(optional) grated hard cheese", "(optional) gold raisins or sweetened cranberries", "(optional) assorted veggies, sliced thin", "salt, pepper, etc to taste"], "recipeInstructions": [{"text": "Wash and thinly slice the brussels sprouts (or buy them pre-sliced at HEB or Central Market)"}, {"text": "Toss the brussels (+ optional veggies), olive oil, and salt + pepper on a baking sheet"}, {"text": "Roast under the broiler for 10-15 mins, keeping a close eye"}, {"text": "Remove from the oven when the edges just start to blacken"}, {"text": "Toss with optional ingredients (depending on how healthy you're feeling)"}], "totalTime": "None", "slug": "roasted-brussels-sprouts", "categories": [], "tags": ["sides", " vegetables"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "32": {"name": "Tequila, Beer, and Citrus Cocktail", "description": "The holiday cocktail that takes you straight to the beach (and isn\u2019t that where you want to be anyway?). ", "image": "tequila-beer-and-citrus-cocktail.jpg", "recipeYield": "Makes 1", "recipeIngredient": ["\u00be oz. fresh grapefruit juice", "\u00be oz. fresh lime juice", "\u00bd oz. agave nectar", "2 Tbsp. red pepper jelly", "1\u00bd oz. tequila blanco", "1 12-oz. chilled Mexican beer", "Lime wedge (for serving)"], "recipeInstructions": [{"@type": "HowToStep", "text": "Combine grapefruit juice, lime juice, agave, and jelly in a cocktail shaker filled with ice and shake until incorporated, 15\u201320 seconds. Add tequila and shake 10 seconds longer. Fill a tall glass to the top with ice. Pour in beer just to fill the glass halfway. Double-strain grapefruit mixture into glass; garnish with lime wedge."}], "totalTime": "None", "slug": "tequila-beer-and-citrus-cocktail", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/tequila-beer-and-citrus-cocktail", "extras": {}}, "33": {"name": "Ginger-Citrus Cookies", "description": "Blanching the citrus peel in several changes of boiling water removes bitterness while still leaving plenty of bright flavor behind.", "image": "ginger-citrus-cookies.jpg", "recipeYield": "Makes about 28", "recipeIngredient": ["2 cups 1\"\u20132\"-wide strips lemon, lime, orange, or grapefruit zest (pith removed)", "2 cups granulated sugar", "1 tsp. ground ginger", "\u00bd tsp. ground cardamom", "\u00bd tsp. ground cinnamon", "\u00bc tsp. ground cloves", "3\u00bd cups all-purpose flour", "1 Tbsp. baking soda", "1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt", "\u00bd tsp. freshly ground white pepper", "10 Tbsp. unsalted butter, room temperature", "1 cup granulated sugar", "\u00bd cup (packed) light brown sugar", "2 large eggs", "\u00be cup mild-flavored (light) molasses"], "recipeInstructions": [{"@type": "HowToStep", "text": "Place citrus zest in a medium saucepan and pour in cold water to cover. Bring to a boil; drain. Repeat process 2 more times."}, {"@type": "HowToStep", "text": "Combine sugar and 2 cups water in same saucepan; bring to a boil, stirring to dissolve sugar. Add citrus zest and bring just to a simmer. Reduce heat to low and simmer very gently until peels are translucent, 30\u201345 minutes. Remove from heat and let citrus zest cool in syrup."}, {"@type": "HowToStep", "text": "Drain citrus zest and finely chop. Spread out on a rimmed baking sheet; let sit until dry."}, {"@type": "HowToStep", "text": "Preheat oven to 350\u00b0. Toast ginger, cardamom, cinnamon, and cloves in a small skillet over medium heat, stirring with a wooden spoon, until fragrant, about 2 minutes. Remove from heat."}, {"@type": "HowToStep", "text": "Sift toasted spices, flour, baking soda, salt, and white pepper into a medium bowl or onto a sheet of wax paper."}, {"@type": "HowToStep", "text": "Using an electric mixer on medium-high speed, beat butter and both sugars in a large bowl until light and fluffy, about 4 minutes. Add eggs one at a time, beating well after each addition and scraping down sides of bowl as needed. Beat in molasses. Gradually add dry ingredient in 2 batches, beating on low speed to incorporate after each addition. Stir in 1 cup candied citrus peel."}, {"@type": "HowToStep", "text": "Drop dough by rounded tablespoons between 2 parchment-lined baking sheets, spacing 2\" apart. Bake cookies until tops feel firm when lightly touched, 10\u201312 minutes. Let cool on baking sheets about 2 minutes, then transfer to a wire rack and let cool completely."}], "totalTime": "None", "slug": "ginger-citrus-cookies", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 3, "orgURL": "https://www.bonappetit.com/recipe/ginger-citrus-cookies", "extras": {}}, "34": {"name": "Marinated Tofu With Brussels Sprouts and Farro", "description": "The soul of this recipe comes from the ginger and tamari marinade that gives a sweet and savory winter coat to the crispy tofu. ", "image": "marinated-tofu-with-brussels-sprouts-and-farro.jpg", "recipeYield": "4 servings", "recipeIngredient": ["1 cup semi-pearled farro or cracked freekeh, rinsed", "Kosher salt", "1 14-oz. block extra-firm tofu", "1 1\u00bd\" piece ginger, peeled, finely\u00a0 grated", "2 Tbsp. tamari or low-sodium soy\u00a0 sauce", "1 Tbsp. fish sauce", "1 tsp. ground cumin", "6 scallions, coarsely chopped", "12 oz. brussels sprouts, trimmed,\u00a0 halved through stem end", "4 Tbsp. vegetable oil, divided\u00bd lemon", "\u2153 cup coarsely chopped parsley or cilantro"], "recipeInstructions": [{"text": "Preheat oven to 425\u00b0. Toast farro in a large wide pot over medium heat,\u00a0 stirring often, until golden brown, about 4 minutes. Remove from heat and pour in cold water to cover grains by 1\"; season generously with salt. Set pot over medium-high heat and bring water to a boil. Reduce heat and simmer, skimming foam from surface, until grains are tender but still have some bite, 25\u201330 minutes. Drain farro and return to pot off heat; cover to keep warm."}, {"text": "While farro is cooking, cut tofu lengthwise to create 2 wide, flat slabs.\u00a0 Pat dry with paper towels to remove as much moisture from surface as possible. Arrange in a single layer in a large shallow bowl."}, {"text": "Whisk ginger, tamari, fish sauce, and cumin in a small bowl to combine. Pour half of marinade over tofu and gently turn to coat evenly."}, {"text": "Toss scallions and brussels sprouts with 2 Tbsp. oil and remaining marinade on a large rimmed baking sheet to coat well; season with salt. Spread out vegetables to ensure everything cooks evenly and roast, tossing halfway through, until deeply browned in spots, 20\u201325 minutes."}, {"text": "Finely grate zest from lemon over vegetables, then squeeze juice over. Add parsley and toss well to combine."}, {"text": "Heat remaining 2 Tbsp. oil in a large nonstick skillet over medium-high. Working in batches if needed, cook tofu, undisturbed, until dark brown and very crisp, about 2 minutes. Carefully turn over and cook on other side until dark brown and very crisp, about 2 minutes. Transfer tofu to a cutting board and slice as desired."}, {"text": "Divide farro and tofu evenly among bowls. Scatter brussels sprouts mixture on top."}], "totalTime": "None", "slug": "marinated-tofu-with-brussels-sprouts-and-farro", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/marinated-tofu-with-brussels-sprouts-and-farro", "extras": {}}, "35": {"name": "Green Spaghetti", "description": "We saw this on Jamie Oliver's TV show and made it that night. He calls for **cavolo nero** as the greens, but dinosaur kale or spinach or any dark leafy green will do. The bigger the green, the easier it is to fish out of the boiling water, so keep that in mind.\nStart to finish this whole recipe takes about 10 minutes, but it's pretty rapid fire once you get stated. Have plates and an appetite standing by.", "image": "green-spaghetti.jpg", "recipeYield": "", "recipeIngredient": ["dried spaghetti", "giant bunch of dark greens (see notes)", "4 cloves of garlic", "1/4 cup parmesan cheese", "1.4 cup ricotta or queso fresco"], "recipeInstructions": [{"text": "Bring water to a boil and add spaghetti, greens, and whole garlic cloves"}, {"text": "Wilt the greens for a few minutes"}, {"text": "Use tongs to move greens and garlic to a blender, add fresh-grated parmesan, pulse for a few mins, add salt and pepper to taste"}, {"text": "Drain pasta, reserving a cup or so of the water"}, {"text": "Toss pasta in sauce, adding a splash of water to make everything happy"}, {"text": "Plate and top with freshly crumbled cheese and more cracked black pepper"}], "totalTime": "None", "slug": "green-spaghetti", "categories": [], "tags": ["mains", " pasta", " veg"], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 0, "orgURL": null, "extras": {}}, "36": {"name": "Five Spice Popcorn Chicken", "description": "It\u2019s easy to rely on take-out for some of our favorite Chinese dishes. However, with the right pantry staples, dishes like this Five Spice Popcorn Chicken can become part of your go-to arsenal of recipes. This crispy chicken is coated in a creamy, tangy sauce, made zesty with The Spice Hunter Chinese Five Spice, a blend of star anise, cloves, cinnamon, fennel, and black pepper.", "image": "five-spice-popcorn-chicken.jpg", "recipeYield": "Serves 4 People", "recipeIngredient": ["1 tablespoon soy sauce", "1 tablespoon sugar", "1 teaspoon The Spice Hunter\u00ae Chinese Five Spice Blend, plus more for serving", "1 clove garlic, finely grated", "1 1/2 pounds boneless skinless chicken thighs, cut into roughly 1-inch chunks", "\u2153 cup cornstarch", "1 large egg, beaten", "\u00be cup all-purpose flour", "Canola or vegetable oil, for frying", "Flaky sea salt", "Scallion, thinly sliced, for serving", "Sriracha mayonnaise, for serving, optional"], "recipeInstructions": [{"@type": "HowToStep", "text": "In a medium bowl, whisk the soy sauce with the sugar, Chinese Five Spice, and garlic. Add the chicken and toss to coat. Let marinate 15 minutes."}, {"@type": "HowToStep", "text": "Drain any excess marinade off of the chicken and toss the chicken with the cornstarch to coat. Once fully coated, add the beaten egg and toss to coat."}, {"@type": "HowToStep", "text": "In a large heavy bottomed pot, heat 1-inch of oil to 350."}, {"@type": "HowToStep", "text": "Place the flour in a large ziploc bag. Working in batches, transfer a few chicken pieces into the bag with the flour and toss to coat, then remove, leaving excess flour in the bag."}, {"@type": "HowToStep", "text": "Carefully place the breaded chicken in the hot oil and fry, turning occasionally, until golden and cooked through about 3 to 4 minutes."}, {"@type": "HowToStep", "text": "Using a slotted spoon or spider, transfer the cooked chicken to a paper towel lined plate. Season with salt and additional Chinese Five Spice seasoning. Repeat the flouring and frying with remaining chicken."}, {"@type": "HowToStep", "text": "Serve with scallions, more Chinese Five Spice Blend, and optional sriracha mayonnaise."}], "totalTime": "None", "slug": "five-spice-popcorn-chicken", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 3, "orgURL": "https://www.bonappetit.com/recipe/five-spice-popcorn-chicken#intcid=_bon-appetit-recipe-bottom-recirc_3cad5ce9-734a-46f8-b503-78c33d2e7279_similar2-3", "extras": {}}, "37": {"name": "Green Seasoning Baked Cod", "description": "Herbaceous, aromatic, fresh, and\u2014maybe most importantly\u2014simple, this Trini-inspired recipe from Brigid Washington is just the cure for those January blues.", "image": "green-seasoning-baked-cod.jpg", "recipeYield": "4 servings", "recipeIngredient": ["\u00bc Vidalia or other sweet onion", "4 6-oz. skinless, boneless cod fillets", "1\u00bd tsp. Diamond Crystal or \u00be tsp. Morton kosher salt, divided", "Freshly ground black pepper", "\u00bc cup plus 2 tsp. extra-virgin olive oil", "1 lime", "4 garlic cloves", "1 2\" piece ginger", "4\u20135 thyme sprigs", "1 small bunch cilantro", "2\u20134 scallions", "1 red bell pepper", "1 habanero chile", "Cooked brown rice or ramen noodles (for serving)"], "recipeInstructions": [{"text": "Preheat oven to 300\u00b0. Thinly slice \u00bc Vidalia or other sweet onion with a chef\u2019s knife and place in a medium baking dish. Place four 6-oz. skinless, boneless cod fillets on top of onion slices. Generously sprinkle fillets on both sides with 1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt and season with freshly ground black pepper. Pour \u00bc cup extra-virgin olive oil over, then finely grate zest from 1 lime on top; set lime aside. Turn fillets to coat. Let cod sit while you prepare remaining ingredients."}, {"text": "Smash, peel, and coarsely chop 4 garlic cloves. Peel one 2\" piece ginger with a spoon, then thinly slice; place in a small bowl. Cut slices into matchsticks; cut matchsticks into small cubes. Place garlic and ginger in a small bowl."}, {"text": "Pick leaves off 4\u20135 thyme sprigs and chop; discard stems. You should have about 1 Tbsp. chopped thyme. Coarsely chop leaves and tender stems from 1 small bunch cilantro until you have \u00bd cup (reserve remaining cilantro for another use); add herbs to bowl with garlic and ginger."}, {"text": "Trim and coarsely chop 2\u20134 scallions to yield about \u2153 cup and place in same bowl."}, {"text": "Slice 1 red bell pepper in half lengthwise and remove ribs and seeds; discard. Finely chop flesh and add to bowl. Slice 1 habanero chile in half lengthwise and remove ribs and seeds; discard. Finely chop half of chile; reserve remaining chile for another use. Add bell pepper and chile to bowl and toss all ingredients with your hands to combine."}, {"text": "Spoon mixture evenly over cod to completely cover. Slice reserved lime in half and squeeze juice over; sprinkle with remaining \u00bd tsp. Diamond Crystal or \u00bc tsp. Morton kosher salt and drizzle with remaining 2 tsp. extra-virgin olive oil. Bake until fish is opaque and cooked through and red pepper and chile are softened, 25\u201335 minutes. Serve cod with cooked brown rice or ramen noodles."}], "totalTime": "None", "slug": "green-seasoning-baked-cod", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/green-seasoning-baked-cod", "extras": {}}, "38": {"name": "Huevos Rancheros con Rajas y Champi\u00f1ones", "description": "For his take on this classic Mexican dish, which originated as a simple but hearty ranchers' breakfast, Rick Martinez adds a layer of cheesy saut\u00e9ed poblanos and mushrooms to the tostada-egg-salsa trio.", "image": "huevos-rancheros-con-rajas-y-champinones.jpg", "recipeYield": "4-6 servings", "recipeIngredient": ["1 serrano chile", "16 oz. cherry tomatoes (about 3 cups)", "\u00bc onion", "\u00bc cup cilantro leaves with tender stems, plus more for serving", "3 tsp. Diamond Crystal or 1\u00bd tsp. Morton kosher salt, divided, plus more", "1\u00bd tsp. (or more) fresh lime juice", "8 oz. button mushrooms", "2 medium poblano chiles or any color bell peppers", "2 garlic cloves", "6 oz. white cheddar", "5 Tbsp. extra-virgin olive oil, divided", "4 large eggs", "4 tostadas"], "recipeInstructions": [{"text": "Make the salsa ranchera: Remove stem from 1 serrano chile (only use \u00bd if you don\u2019t want your salsa spicy) and halve lengthwise. Heat a medium skillet, preferably cast iron, over high until you see wisps of smoke, about 2 minutes. Arrange serrano chile, skin side down, on one side of pan and add 16 oz. cherry tomatoes. Cook, leaving chile undisturbed and occasionally tossing tomatoes, until charred, 6\u20138 minutes. It is going to get a little smoky so turn on the vent and open a window. Transfer serrano chile and tomatoes to a blender or food processor. Reserve skillet."}, {"text": "Add \u00bc onion, \u00bc cup cilantro leaves with tender stems, and 1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt to blender and blend on low speed (or pulse if using a food processor) until vegetables are broken up, about 30 seconds. Increase speed to medium-low and pur\u00e9e until salsa is almost smooth but some chunks remain. Don\u2019t be tempted to blend on high or you will incorporate air into the salsa and it will look and taste more like a smoothie than a salsa. Transfer to a medium bowl and stir in 1\u00bd tsp. fresh lime juice. Taste and season with more salt and add more lime juice if needed. Set aside until ready to serve."}, {"text": "Using a paper towel, brush off dirt from 8 oz. button mushrooms. Trim woody stems and discard. Cut mushrooms into quarters. Cut 2 medium poblano chiles or any color bell peppers in half lengthwise and remove stems, seeds, and ribs. Cut chiles into long thin strips about \u00bc\" thick. Use the side of a chef\u2019s knife to lightly smash 2 garlic cloves, then peel and thinly slice. Grate 6 oz. white cheddar on the large holes of a box grater."}, {"text": "Head over to the stove with all of your prep work (gathering it on a rimmed baking sheet will make this easy!). Now, you\u2019re ready to make the mushroom topping. Wipe out reserved skillet with a paper towel and heat 3 Tbsp. extra-virgin olive oil over medium-high. Arrange mushrooms in a single layer in skillet and cook, undisturbed, until brown underneath, about 3 minutes. Give mushrooms a toss and season with 1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt. Continue to cook, tossing occasionally, until deep golden brown all over, 5\u20137 minutes longer. Using a slotted spoon, transfer to a small bowl, leaving oil behind."}, {"text": "Arrange poblano chiles in an even layer in same skillet and cook, undisturbed, until browned underneath, about 2 minutes. Add garlic and season with 1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt. Cook, tossing occasionally, until lightly browned all over, 5\u20137 minutes longer. Add 3 Tbsp. water and cook, stirring occasionally, until water has evaporated, about 2 minutes. The water will soften the chiles and pull up all those tasty brown bits from the bottom of the pan."}, {"text": "Add mushrooms with any accumulated juices back to pan and toss until evenly distributed. Sprinkle cheese over in an even layer and remove skillet from heat. Cover (a baking sheet works great if you don\u2019t have a lid that will work) and let sit 5 minutes for cheese to melt."}, {"text": "You are almost there; you just need to cook the eggs. Heat remaining 2 Tbsp. extra-virgin olive oil in a large nonstick skillet over medium-high. Crack 4 large eggs into skillet, leaving space around each one, and cook until whites are set and edges are crisp, about 4 minutes. Season with salt."}, {"text": "To serve, spoon cheesy mushroom mixture onto 4 tostadas, dividing evenly, and top each with an egg, then reserved salsa and some cilantro."}], "totalTime": "None", "slug": "huevos-rancheros-con-rajas-y-champinones", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/huevos-rancheros-con-rajas-y-champinones", "extras": {}}, "39": {"name": "Bon App\u00e9tit's Perfect Pizza", "description": "With the amount of love, time, and research that went into making what constitutes the \u201cperfect\u201d pizza, you\u2019ll definitely want to share. Welcome to the best-ever excuse to invite people over for a pizza party.", "image": "bon-appetit-s-perfect-pizza.jpg", "recipeYield": "Makes one 10\"-diameter pie", "recipeIngredient": ["250 g Central Milling Organic High Mountain flour", "150 g freshly milled flour (from Central Milling Organic Steel Cut Cracked Wheat) or Central Milling Organic High Mountain flour or high-quality whole wheat flour", "30 g Diamond Crystal kosher salt", "600 g King Arthur all-purpose flour, plus more for dusting", "150 g sourdough starter", "50 g Frankie\u2019s extra-virgin olive oil", "5 g fresh cake yeast, finely crumbled", "2 28-oz. cans Bianco DiNapoli whole peeled tomatoes", "3 thin slices of garlic (not 3 garlic cloves, thinly sliced!)", "2 Tbsp. Frankie\u2019s extra-virgin olive oil", "\u00bc tsp. kosher salt", "1 oz. imported mozzarella di bufala (such as Antiche Bont\u00e0 Mozarella di bufala Campana), torn into quarter-size pieces", "1 oz. fior di latte mozzarella, torn into quarter-size pieces", "2 Tbsp. extra-virgin olive oil, plus more", "4 oz. maitake mushrooms, torn into 1\" pieces", "Kosher salt, freshly ground pepper", "2 Calabrian chiles from a jar, coarsely chopped", "1 200 g ball Pizza Dough (see above)", "King Arthur all-purpose flour (for dusting)", "3 Tbsp. Pizza Sauce (see above)", "\u00bd garlic clove, very thinly sliced", "4 oz. 2-year-aged Parmesan, coarsely grated", "6 basil leaves, torn", "1 lemon", "A Breville Pizzaiolo oven; a wooden pizza peel (no more than 10\" wide and \u00bc\" thick); a kitchen scale; a bench scraper; a food mill"], "recipeInstructions": [{"@type": "HowToStep", "text": "Mix High Mountain flour, freshly milled flour, salt, and 600 g all-purpose flour in the bowl of a stand mixer fitted with the dough hook just to combine."}, {"@type": "HowToStep", "text": "Whisk starter and 660 g room-temperature water in a large measuring glass to combine, then add oil and yeast and whisk until well blended."}, {"@type": "HowToStep", "text": "Make a well in dry ingredients and pour in starter mixture. Mix on low speed, increasing speed to medium as dry ingredients are incorporated, until well combined. Scrape down sides of bowl and fold in any flour, if needed. Remove bowl from mixer and scrape dough from hook. Cover bowl with plastic wrap; let dough rest at room temperature for 30 minutes."}, {"@type": "HowToStep", "text": "Uncover dough and fit bowl back onto mixer. Mix on medium speed until you're able to pinch and stretch a small piece of dough between your fingers until translucent and doesn\u2019t rip, about 10 minutes (dough should look sticky but cohesive, and it should be very elastic and jiggly). Transfer dough to a very large bowl, cover tightly with plastic wrap, and let rise in a warm, draft-free spot (near the oven is great) until 50 percent expanded in volume, about 3 hours."}, {"@type": "HowToStep", "text": "Turn out dough onto a lightly floured surface. Lightly flour your hands, scale, bench scraper, and 2 small rimmed baking sheets."}, {"@type": "HowToStep", "text": "Divide dough into 200 g portions. Working one at a time, form each portion into a tight, even ball, rotating on counter and tucking under and pinching on bottom of ball to seal. Try to handle the ball as little as possible to keep dough from deflating. Using bench scraper, place balls seam side down on floured baking sheets, spacing evenly apart. Lightly sprinkle dough with some flour and cover baking sheets tightly with plastic as you go. Chill at least 24 hours and up to 48 before using."}, {"@type": "HowToStep", "text": "Do Ahead: Dough can be made 2 weeks ahead. Wrap balls tightly with plastic wrap and freeze. Let thaw in refrigerator overnight. Note: The frozen dough won't bake with as many bubbles or be as light as the fresh dough. It might also be stickier and harder to work with. Because of the idiosyncrasies of using starter, the success of using frozen dough will vary."}, {"@type": "HowToStep", "text": "Place tomatoes (without juice from can) in a colander to drain. Pass tomatoes through a food mill (using the medium disk) into a medium bowl (you should have about 2 cups pur\u00e9e). Mix in garlic, oil, and salt. Let sit at least 30 minutes before using."}, {"@type": "HowToStep", "text": "Do Ahead: Sauce can be made 2 days ahead. Cover and chill."}, {"@type": "HowToStep", "text": "Preheat a Breville Pizzaiolo oven to 700\u00b0. Place mozzarella and fior di latte in a fine-mesh sieve set over a medium bowl and let sit 30 minutes to drain."}, {"@type": "HowToStep", "text": "Drizzle a little oil in a large skillet and swirl pan to lightly coat. Heat over medium-high. Add mushrooms in a single layer and cook, undisturbed, until lightly browned underneath and starting to soften, about 3 minutes. Season with salt and pepper and toss mushrooms. Continue to cook, tossing occasionally, until lightly browned all over and softened, about 3 minutes more. Remove from heat and set aside."}, {"@type": "HowToStep", "text": "Place chiles in a small bowl and mix in just enough oil to make a tight sauce; set aside."}, {"@type": "HowToStep", "text": "Use a bench scraper to swiftly lift a ball of Pizza Dough from baking sheet, then invert it into a large bowl of flour, seam side up. Pass dough back and forth between your hands to knock off some flour. Place domed side up on work surface and use your fingertips to press dimples in dough, working from the center toward the outer edge and maintaining a circular shape. Stop dimpling 1\" before outer edge of dough (this will become the crust, and you want it to rise and puff higher than the center of the dough). After dimpling, stretch dough over the backs of your hands, moving in a circular pattern to create a 10\" round. Place onto very lightly floured peel."}, {"@type": "HowToStep", "text": "Spoon Pizza Sauce onto center of dough. Working in a spiral from the center outward, use spoon to spread sauce all the way to the inner edge of the crust."}, {"@type": "HowToStep", "text": "Top pizza with drained mozzarella and fior di latte, then garlic. Scatter several pieces of reserved mushroom around and sprinkle 1 Tbsp. Parmesan over. Slide pizza into oven and cook, rotating halfway through, until crust is puffed and charred in spots, cheese is melted, and underside of crust is golden brown with some darker leopard spots, 3 minutes. Remove pizza from oven. Drizzle with reserved chile sauce and sprinkle with another 1\u00bd tsp. Parmesan. Arrange basil on top and finely grate a light dusting of lemon zest over."}], "totalTime": "None", "slug": "bon-appetit-s-perfect-pizza", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 3, "orgURL": "https://www.bonappetit.com/recipe/perfect-pizza", "extras": {}}, "40": {"name": "Braised Beans and Sardines With Fennel", "description": "Tinned sardines add briney flavor (and protein!)\u2014leave them whole or break them up and fold them into the soup.", "image": "braised-beans-and-sardines-with-fennel.jpg", "recipeYield": "4 servings", "recipeIngredient": ["1 fennel bulb with fronds, stalks and fronds removed", "1 lemon, halved", "\u00bc cup extra-virgin olive oil", "2 medium shallots, thinly sliced", "6 garlic cloves, thinly sliced", "Small handful of mixed hardy herb sprigs (such as bay leaves, thyme, and/or rosemary)", "\u00bd tsp. crushed red pepper flakes", "\u00bc cup dry white wine", "6 cups low-sodium chicken broth", "2 15-oz. cans cannellini (white kidney) or cranberry beans, rinsed1 4.4-oz. tin oil-packed sardines, drained", "1 (loosely packed) cup very coarsely chopped parsley", "Kosher salt", "Toasted seeded bread (for serving)"], "recipeInstructions": [{"text": "Slice fennel bulb in half lengthwise and cut each half lengthwise into 3 wedges. Thinly slice 1 lemon half into rounds and wriggle out and discard any seeds; leave remaining half intact and set aside."}, {"text": "Heat oil in a medium pot over medium-high. Add fennel, shallots, garlic, hardy herbs, lemon rounds, and red pepper flakes and cook, stirring occasionally, until fennel and lemon are softened slightly and golden brown in spots, 5\u20137 minutes."}, {"text": "Using tongs, transfer lemon rounds to a small bowl; set aside. Add wine to pot and cook until reduced by half, about\u00a0 2 minutes. Pour in broth and bring to a boil. Reduce heat to medium-low and simmer, stirring occasionally, until fennel is tender, about 5 minutes. Add beans and simmer until beans soak up some of the broth and are warmed through, 8\u201310 minutes."}, {"text": "Meanwhile, working one at a time, slice open each sardine with the tip of a paring knife and remove any visible bones; discard. Separate fillets from one another and place in a small bowl. Squeeze juice from remaining reserved lemon half over fillets."}, {"text": "Fish out and discard any hardy herbs and stems you can from braise. Stir in parsley; taste and season broth with more salt if needed."}, {"text": "Divide braise among bowls; top with reserved lemon slices and sardines. Serve with bread alongside."}], "totalTime": "None", "slug": "braised-beans-and-sardines-with-fennel", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210111", "notes": [], "rating": 0, "orgURL": "https://www.bonappetit.com/recipe/braised-beans-and-sardines-with-fennel", "extras": {}}, "41": {"name": "Corn and Crab Beignets With Yaji Aioli", "description": "This ode to chef BJ Dennis and Gullah Geechee cuisine marries crabmeat with corn kernels for a crispy party starter. ", "image": "corn-and-crab-beignets-with-yaji-aioli.jpg", "recipeYield": "8 - 10 Servings", "recipeIngredient": ["\u00bd cup unsalted dry-roasted peanuts", "2 Tbsp. ground ginger", "1 tsp. cayenne pepper", "1 tsp. Diamond Crystal or \u00bd tsp. Morton kosher salt", "1 tsp. garlic powder", "1 tsp. onion powder", "1 tsp. paprika", "2 large egg yolks*", "1 Tbsp. fresh lemon juice", "\u00be cup peanut oil", "1 small shallot, finely chopped", "1 garlic clove, finely grated", "2 Tbsp. thinly sliced chives", "Kosher salt", "4 Tbsp. unsalted butter", "10 oz. frozen corn (about 2 cups)", "2 Tbsp. thinly sliced chives", "1\u00bd cups all-purpose flour", "\u00bd cup cornmeal", "2 tsp. baking powder", "1\u00bd tsp. Diamond Crystal or \u00be tsp. Morton kosher salt", "\u00bd tsp. cayenne pepper", "1 large egg, beaten to blend", "1 cup buttermilk", "8 oz. lump crabmeat, picked over", "Vegetable oil (for frying; about 6 cups)", "* Raw egg is not recommended for the elderly, pregnant women, people with weakened immune systems\u2026or people who don\u2019t like raw egg.", "A deep-fry thermometer"], "recipeInstructions": [{"@type": "HowToStep", "text": "Pulse peanuts in a food processor until very finely chopped (be careful not to go too far; if you overprocess, the peanuts will turn into butter). Add ginger, cayenne, salt, garlic powder, onion powder, and paprika and pulse until powderlike.\nDo ahead: Yaji can be made 3 months ahead. Transfer to an airtight container; cover and chill or freeze."}, {"@type": "HowToStep", "text": "Whisk egg yolks and lemon juice in a medium bowl to combine. Whisking constantly, add oil, starting with just a few drops at a time and gradually increasing to a fine steady stream. Whisk until all of the oil is incorporated and mixture is emulsified, about 4 minutes. Whisk in shallot, garlic, chives, and 1 Tbsp. yaji spice blend. Taste and season with salt.\nDo ahead: Aioli can be made 3 days ahead. Cover and chill."}, {"@type": "HowToStep", "text": "Melt butter in a medium high-sided skillet over medium heat. Add corn and cook, stirring occasionally, until softened slightly and bright yellow, about 5 minutes. Transfer to a large bowl and stir in chives; let cool."}, {"@type": "HowToStep", "text": "Meanwhile, whisk flour, cornmeal, baking powder, salt, and cayenne in a medium bowl."}, {"@type": "HowToStep", "text": "Add egg and buttermilk to corn mixture and stir to combine. Add dry ingredients and stir again just to incorporate, then fold in crabmeat."}, {"@type": "HowToStep", "text": "Pour in oil to come 1\u00bd\" up sides of a large pot or deep fryer. Fit pot with thermometer and heat oil over medium-high to 350\u00b0. Place a wire rack inside a rimmed baking sheet and line rack with paper towels. Working in 4 or 5 batches, scoop out heaping tablespoons of batter, carefully place in oil (8\u201310 per batch) and fry, turning often, until beignets are golden brown and cooked through, about 5 minutes. Transfer beignets to prepared rack; season with salt."}, {"@type": "HowToStep", "text": "Serve beignets warm with yaji aioli for dipping."}], "totalTime": "None", "slug": "corn-and-crab-beignets-with-yaji-aioli", "categories": [], "tags": [], "dateAdded": "{TinyDate}:20210112", "notes": [], "rating": 5, "orgURL": "https://www.bonappetit.com/recipe/corn-and-crab-beignets-with-yaji-aioli", "extras": {}}}, "settings": {"1": {"name": "main", "webhooks": {"webhookTime": "00:00", "webhookURLs": [], "enabled": "false"}}}} \ No newline at end of file diff --git a/mealie/data/db/recipes.json b/mealie/data/db/recipes.json index e4ed5e6d4..e69de29bb 100644 --- a/mealie/data/db/recipes.json +++ b/mealie/data/db/recipes.json @@ -1 +0,0 @@ -{"_default": {"3": {"name": "Chicken and Rice With Leeks and Salsa Verde", "description": "This one-skillet dinner gets deep oniony flavor from lots of leeks cooked down to jammy tenderness.", "image": "chicken-and-rice-with-leeks-and-salsa-verde.jpg", "recipeYield": "4 Servings", "recipeIngredient": ["1\u00bd lb. skinless, boneless chicken thighs (4\u20138 depending on size)", "Kosher salt, freshly ground pepper", "3 Tbsp. unsalted butter, divided", "2 large or 3 medium leeks, white and pale green parts only, halved lengthwise, thinly sliced", "Zest and juice of 1 lemon, divided", "1\u00bd cups long-grain white rice, rinsed until water runs clear", "2\u00be cups low-sodium chicken broth", "1 oil-packed anchovy fillet", "2 garlic cloves", "1 Tbsp. drained capers", "Crushed red pepper flakes", "1 cup tender herb leaves (such as parsley, cilantro, and/or mint)", "4\u20135 Tbsp. extra-virgin olive oil"], "recipeInstructions": [{"text": "Season chicken with salt and pepper. Melt 2 Tbsp. butter in a large high-sided skillet over medium-high heat. Add leeks and half of lemon zest, season with salt and pepper, and mix to coat leeks in butter. Reduce heat to medium-low, cover, and cook, stirring occasionally, until leeks are somewhat tender, about 5 minutes. Remove lid, increase heat to medium-high, and cook, stirring occasionally, until tender and just starting to take on color, about 3 minutes. Add rice and cook, stirring often, 3 minutes, then add broth, scraping up any browned bits. Tuck short sides of each chicken thigh underneath so they are touching and nestle seam side down into rice mixture. Bring to a simmer. Cover, reduce heat to medium-low, and cook until rice is tender and chicken is cooked through, about 20 minutes. Remove from heat. Cut remaining 1 Tbsp. butter into small pieces and scatter over mixture. Re-cover and let sit 10 minutes."}, {"text": "Meanwhile, pulse anchovy, garlic, capers, a few pinches of red pepper flakes, and remaining lemon zest in a food processor until finely chopped. Add herbs; process until a paste forms. With motor running, gradually stream in oil until loosened to a thick sauce. Add half of lemon juice; season salsa verde with salt."}, {"text": "Drizzle remaining lemon juice over chicken and rice. Serve with salsa verde."}], "totalTime": "None", "slug": "chicken-and-rice-with-leeks-and-salsa-verde", "categories": [], "tags": [], "dateAdded": null, "notes": [], "rating": null, "orgURL": "https://www.bonappetit.com/recipe/chicken-and-rice-with-leeks-and-salsa-verde", "extras": {}}}} \ No newline at end of file diff --git a/mealie/data/debug/last_recipe.json b/mealie/data/debug/last_recipe.json index b30f29eed..bd40085b2 100644 --- a/mealie/data/debug/last_recipe.json +++ b/mealie/data/debug/last_recipe.json @@ -1,33 +1,25 @@ { "@context": "http://schema.org", "@type": "Recipe", - "articleBody": "Leftover rice is ideal for this dish (and a great way to use up any takeout that\u2019s hanging around), since fully chilled rice tends to be drier and will become crispier and browner in the skillet. To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it out like a pancake. Don\u2019t touch until you hear it crackle! Finish with a sunny-side-up egg\u2014or poach it if you don't mind the stovetop fuss. This recipe is part of the 2021\u00a0Feel Good Food Plan, our eight-day dinner plan for starting the year off right.", - "alternativeHeadline": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle!", - "dateModified": "2021-01-11 23:25:22.997000", - "datePublished": "2021-01-01 06:00:00", + "articleBody": "At her L.A. bakery Friends and Family, Roxana Jullapat bakes these blondies in a round cake pan, which ensures that each slice has a chewy, toasted edge and a soft center. \u201cThis caramel-flavored treat demonstrates the powerful pairing of brown butter and barley,\u201d Roxana writes in her cookbook Mother Grains (out April 2021). Barley flour gives baked goods a silky, chewy texture, dense crumb, and butterscotch-y flavor. Want to experiment? Make these with 3\u20444 cup (80 g) einkorn flour in place of the barley flour for an even chewier blondie.", + "alternativeHeadline": "Barley flour gives these blondies a chewy texture and butterscotch-like flavor.", + "dateModified": "2021-01-11 14:23:12.455000", + "datePublished": "2021-01-12 04:00:00", "keywords": [ "recipes", - "healthyish", - "salad", - "ginger", - "garlic", - "orange", "oil", - "soy sauce", - "lemon juice", - "sesame oil", + "macadamia nut", + "vanilla", + "butter", + "flour", + "barley", "kosher salt", - "broccoli", - "brown rice", + "brown sugar", "egg", - "celery", - "cilantro", - "mint", - "feel good food plan 2021", - "feel good food plan", + "vanilla extract", "web" ], - "thumbnailUrl": "https://assets.bonappetit.com/photos/5fdbe70a84d333dd1dcc7900/1:1/w_1698,h_1698,c_limit/BA1220feelgoodalt.jpg", + "thumbnailUrl": "https://assets.bonappetit.com/photos/5ff4b06a2f7e5df08337ef60/1:1/w_1005,h_1005,c_limit/Mother-Grains-Macadamia-and-Brown-Butter-Blondies.jpg", "publisher": { "@context": "https://schema.org", "@type": "Organization", @@ -51,68 +43,49 @@ "author": [ { "@type": "Person", - "name": "Devonn Francis", - "sameAs": "https://bon-appetit.com/contributor/devonn-francis/" + "name": "Roxana Jullapat", + "sameAs": "https://bon-appetit.com/contributor/roxana-jullapat/" } ], - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": 4.33, - "ratingCount": 33 - }, - "description": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle! ", - "image": "crispy-rice-with-ginger-citrus-celery-salad.jpg", - "headline": "Crispy Rice With Ginger-Citrus Celery Salad", - "name": "Crispy Rice With Ginger-Citrus Celery Salad", + "description": "Barley flour gives these blondies a chewy texture and butterscotch-like flavor.", + "image": "macadamia-and-brown-butter-blondies.jpg", + "headline": "Macadamia and Brown Butter Blondies", + "name": "Macadamia and Brown Butter Blondies", "recipeIngredient": [ - "1 2\" piece ginger, peeled, finely grated", - "1 small garlic clove, finely grated", - "Juice of 1 orange", - "2 tbsp. vegetable oil", - "1Tbsp. coconut aminos or low-sodium soy sauce", - "1 Tbsp. fresh lemon juice", - "\u00bc tsp. toasted sesame oil", - "Kosher salt", - "1 medium head of broccoli", - "6 Tbsp. (or more) vegetable oil, divided", - "Kosher salt", - "2 cups chilled cooked brown rice", - "4 large eggs", - "3 celery stalks, thinly sliced on a steep diagonal", - "\u00bd cup cilantro leaves with tender stems", - "\u00bd cup mint leaves", - "Crushed red pepper flakes (for serving)" + "Nonstick vegetable oil spray", + "\u00bd cup (65 g) whole raw or toasted macadamia nuts", + "\u00bd vanilla bean, split lengthwise", + "18 Tbsp. unsalted butter", + "\u00be cup plus 2 Tbsp. (105 g) all-purpose flour", + "\u00be cup (85 g) barley flour", + "1\u00be tsp. baking powder", + "1\u00bd tsp. Diamond Crystal or \u00be tsp. Morton kosher salt", + "1 cup plus 7 Tbsp. (packed; 285 g) dark brown sugar", + "2 large eggs", + "1 tsp. vanilla extract", + "2 pints ice cream of choice (optional)" ], "recipeInstructions": [ { - "text": "Whisk ginger, garlic, orange juice, vegetable oil, coconut aminos, lemon juice, and sesame oil in a small bowl; season with salt and set aside." + "text": "Preheat oven to 350\u00b0. Lightly coat a 9\"-diameter cake pan with nonstick spray and line bottom with a parchment paper round. If using raw macadamia nuts, toast on a rimmed baking sheet, tossing once, until golden, 8\u201310 minutes. Let cool, then coarsely chop." }, { - "text": "Trim about \u00bd\" from woody end of broccoli stem. Peel tough outer layer from stem. Cut florets from stems and thinly slice stems about \u00bd\" thick. Break florets apart with your hands into 1\"\u20131\u00bd\" pieces." + "text": "Scrape vanilla seeds into a small saucepan; add pod and butter. Set over medium-low heat and cook, stirring occasionally, until butter foams, then browns, 6\u20138 minutes. Transfer to a medium bowl, scraping in all of the browned bits. Using tongs, remove and discard vanilla pod." }, { - "text": "Heat 2 Tbsp. oil in a large nonstick skillet over medium. Working in 2 batches if needed, arrange broccoli in a single layer and cook, tossing occasionally, until broccoli is bright green and lightly charred around the edges, about\u00a03 minutes. Transfer to a large plate." + "text": "Whisk all-purpose and barley flours, baking powder, and salt in another medium bowl. Add brown sugar to brown butter and stir to combine. Add eggs one at a time, stirring well after each addition. Stir in dry ingredients, then vanilla extract and nuts. Scrape batter into prepared pan; smooth top." }, { - "text": "Pour 2 Tbsp. oil into same pan and heat over medium-high. Once you see the first wisp of smoke, add rice and season lightly with salt. Using a spatula or spoon, press rice evenly into pan like a pancake. Rice will begin to crackle, but don\u2019t fuss with it. When the crackling has died down almost completely, about\u00a03 minutes, break rice into large pieces and turn over." + "text": "Bake blondies, rotating halfway through, until top is golden brown and a tester inserted into the center comes out clean, 40\u201345 minutes. Let cool." }, { - "text": "Add broccoli back to pan and give everything a toss to combine. Cook, tossing occasionally and adding another\u00a01 Tbsp. oil if pan looks dry, until broccoli is tender and rice is warmed through and very crisp, about 5 minutes. Transfer mixture to a platter or divide among plates and set aside." - }, - { - "text": "Wipe out skillet; heat remaining\u00a02 Tbsp. oil over medium-high. Crack eggs into skillet; season with salt. Oil should bubble around eggs right away. Cook, rotating skillet occasionally, until whites are golden brown and crisp at the edges and set around the yolk (which should be runny), about 2 minutes." - }, - { - "text": "Toss celery, cilantro, and mint with\u00a03 Tbsp. reserved dressing and a pinch of salt in a medium bowl to combine." - }, - { - "text": "Scatter celery salad over fried rice; top with fried eggs and sprinkle with red pepper flakes. Serve extra dressing alongside." + "text": "Turn out blondies, remove parchment, and cut into 12 wedges. Serve each with a scoop of ice cream if desired." } ], - "recipeYield": "4 servings", - "url": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad", - "slug": "crispy-rice-with-ginger-citrus-celery-salad", - "orgURL": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad", + "recipeYield": "Makes 12", + "url": "https://www.bonappetit.com/recipe/macadamia-and-brown-butter-blondies", + "slug": "macadamia-and-brown-butter-blondies", + "orgURL": "https://www.bonappetit.com/recipe/macadamia-and-brown-butter-blondies", "categories": [], "tags": [], "dateAdded": null, diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index bf1a837ed..2310185a5 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -12,10 +12,8 @@ class BaseDocument: self.store: StoreBase self.document: mongoengine.Document - @staticmethod - def _unpack_mongo( - document, - ) -> dict: # TODO: Probably Put a version in each class to speed up reads? + @staticmethod # TODO: Probably Put a version in each class to speed up reads? + def _unpack_mongo(document) -> dict: document = json.loads(document.to_json()) del document["_id"] diff --git a/mealie/db/db_themes.py b/mealie/db/db_themes.py index 82afdb74b..6867c285d 100644 --- a/mealie/db/db_themes.py +++ b/mealie/db/db_themes.py @@ -10,7 +10,8 @@ class _Themes(BaseDocument): self.primary_key = "name" if USE_TINYDB: self.store = tiny_db.themes - self.document = SiteThemeDocument + else: + self.document = SiteThemeDocument def save_new(self, theme_data: dict) -> None: if USE_MONGO: diff --git a/mealie/db/tinydb/baseclass.py b/mealie/db/tinydb/baseclass.py index 698953c26..e4d83f903 100644 --- a/mealie/db/tinydb/baseclass.py +++ b/mealie/db/tinydb/baseclass.py @@ -17,7 +17,7 @@ class StoreBase: ) else: self.store.insert(document) - return document["slug"] + return document def delete(self, document_primary_key: str): self.store.remove(where(self.primary_key) == document_primary_key) diff --git a/mealie/db/tinydb/tinydb_setup.py b/mealie/db/tinydb/tinydb_setup.py index ce0929b2e..90c7c218a 100644 --- a/mealie/db/tinydb/tinydb_setup.py +++ b/mealie/db/tinydb/tinydb_setup.py @@ -1,32 +1,68 @@ +from datetime import date, datetime + from db.tinydb.baseclass import StoreBase from settings import TINYDB_DIR +from tinydb_serialization import SerializationMiddleware, Serializer from tinydb import TinyDB +class DateSerializer(Serializer): + OBJ_CLASS = date # The class handles date objects + + def encode(self, obj): + """ + Serialize the naive date object without conversion. + """ + return obj.strftime("%Y%m%d") + + def decode(self, s): + """ + Return the serialization as a date object. + """ + return datetime.strptime(s, "%Y%m%d").date() + + +class DateTimeSerializer(Serializer): + OBJ_CLASS = datetime # The class this serializer handles + + def encode(self, obj): + return obj.strftime("%Y-%m-%dT%H:%M:%S") + + def decode(self, s): + return datetime.strptime(s, "%Y-%m-%dT%H:%M:%S") + + +serialization = SerializationMiddleware() +serialization.register_serializer(DateTimeSerializer(), "TinyDateTime") +serialization.register_serializer(DateSerializer(), "TinyDate") + + class TinyDatabase: def __init__(self) -> None: - self.recipes = self._Recipes() - self.meals = self._Meals() - self.settings = self._Settings() - self.themes = self._Themes() + self.db = TinyDB(TINYDB_DIR.joinpath("db.json"), storage=serialization) + + self.recipes = self._Recipes(self.db) + self.meals = self._Meals(self.db) + self.settings = self._Settings(self.db) + self.themes = self._Themes(self.db) class _Recipes(StoreBase): - def __init__(self) -> None: + def __init__(self, db) -> None: self.primary_key = "slug" - self.store = TinyDB(TINYDB_DIR.joinpath("recipes.json")) + self.store = db.table("recipes") class _Meals(StoreBase): - def __init__(self) -> None: + def __init__(self, db) -> None: self.primary_key = "uid" - self.store = TinyDB(TINYDB_DIR.joinpath("meals.json")) + self.store = db.table("meals") class _Settings(StoreBase): - def __init__(self) -> None: + def __init__(self, db) -> None: self.primary_key = "name" - self.store = TinyDB(TINYDB_DIR.joinpath("settings.json")) + self.store = db.table("settings") class _Themes(StoreBase): - def __init__(self) -> None: + def __init__(self, db) -> None: self.primary_key = "name" - self._store = TinyDB(TINYDB_DIR.joinpath("themes.json")) + self.store = db.table("themes") diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py index 2dfa2b520..abeaed57c 100644 --- a/mealie/routes/backup_routes.py +++ b/mealie/routes/backup_routes.py @@ -40,11 +40,12 @@ def export_database(data: BackupJob): ) def import_database(file_name: str): """ Import a database backup file generated from Mealie. """ + import_db = ImportDatabase( zip_archive=file_name, import_recipes=True, - import_settings=True, - import_themes=True, + import_settings=False, + import_themes=False, ) imported = import_db.run() diff --git a/mealie/routes/setting_routes.py b/mealie/routes/setting_routes.py index 2cb93f570..df855faff 100644 --- a/mealie/routes/setting_routes.py +++ b/mealie/routes/setting_routes.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, HTTPException -from global_scheduler import scheduler from services.scheduler_services import post_webhooks from services.settings_services import SiteSettings, SiteTheme +from utils.global_scheduler import scheduler from utils.snackbar import SnackResponse router = APIRouter() diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index cedbcc0b9..2b1f7b370 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -74,6 +74,11 @@ class ImportDatabase: recipe_dict = json.loads(f.read()) recipe_dict = ImportDatabase._recipe_migration(recipe_dict) + + recipe_obj = Recipe(**recipe_dict) + recipe_obj.save_to_db() + successful_imports.append(recipe.stem) + logger.info(f"Imported: {recipe.stem}") try: recipe_obj = Recipe(**recipe_dict) recipe_obj.save_to_db() diff --git a/mealie/services/recipe_services.py b/mealie/services/recipe_services.py index f12cf6bb5..255341bbb 100644 --- a/mealie/services/recipe_services.py +++ b/mealie/services/recipe_services.py @@ -115,8 +115,9 @@ class Recipe(BaseModel): pass recipe_doc = db.recipes.save_new(recipe_dict) + recipe = Recipe(**recipe_doc) - return recipe_doc.slug + return recipe.slug @staticmethod def delete(recipe_slug: str) -> str: diff --git a/mealie/services/settings_services.py b/mealie/services/settings_services.py index 4b7040c49..9fcf4c406 100644 --- a/mealie/services/settings_services.py +++ b/mealie/services/settings_services.py @@ -1,9 +1,7 @@ -import json from typing import List, Optional from db.database import db from pydantic import BaseModel -from startup import USE_TINYDB from utils.logger import logger @@ -86,17 +84,6 @@ class SiteTheme(BaseModel): return cls(name=name, colors=colors) - @staticmethod - def _unpack_doc(document): - if USE_TINYDB: - theme_colors = SiteTheme(**document) - else: - document = json.loads(document.to_json()) - del document["_id"] - theme_colors = SiteTheme(**document) - - return theme_colors - @staticmethod def get_all(): all_themes = db.themes.get_all() diff --git a/mealie/settings.py b/mealie/settings.py index 4adef3ce6..4b9dd9c99 100644 --- a/mealie/settings.py +++ b/mealie/settings.py @@ -5,11 +5,13 @@ import dotenv CWD = Path(__file__).parent + # Register ENV ENV = CWD.joinpath(".env") dotenv.load_dotenv(ENV) # Helpful Globals +BASE_DIR = CWD DATA_DIR = CWD.joinpath("data") WEB_PATH = CWD.joinpath("dist") IMG_DIR = DATA_DIR.joinpath("img") @@ -45,13 +47,15 @@ else: # DATABASE ENV -DATABASE_TYPE = os.getenv("db_type", "mongo") # mongo, tinydb -USE_TINYDB = False -USE_MONGO = False +DATABASE_TYPE = os.getenv("db_type", "tinydb") # mongo, tinydb if DATABASE_TYPE == "tinydb": USE_TINYDB = True + USE_MONGO = False + elif DATABASE_TYPE == "mongo": USE_MONGO = True + USE_TINYDB = False + else: raise Exception( "Unable to determine database type. Acceptible options are 'mongo' or 'sqlite' " diff --git a/mealie/startup.py b/mealie/utils/api_docs.py similarity index 77% rename from mealie/startup.py rename to mealie/utils/api_docs.py index 6d0b0391b..926e9359d 100644 --- a/mealie/startup.py +++ b/mealie/utils/api_docs.py @@ -1,17 +1,6 @@ import json -from pathlib import Path -from settings import REQUIRED_DIRS -CWD = Path(__file__).parent - -def pre_start(): - - ensure_dirs() - - -def ensure_dirs(): - for dir in REQUIRED_DIRS: - dir.mkdir(parents=True, exist_ok=True) +from settings import BASE_DIR """Script to export the ReDoc documentation page into a standalone HTML file.""" @@ -42,14 +31,10 @@ HTML_TEMPLATE = """ """ -out_path = CWD.joinpath("temp", "index.html") +out_path = BASE_DIR.joinpath("temp", "index.html") def generate_api_docs(app): with open(out_path, "w") as fd: out_path.parent.mkdir(exist_ok=True) print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd) - - -if __name__ == "__main__": - pass diff --git a/mealie/global_scheduler.py b/mealie/utils/global_scheduler.py similarity index 100% rename from mealie/global_scheduler.py rename to mealie/utils/global_scheduler.py diff --git a/mealie/utils/startup.py b/mealie/utils/startup.py new file mode 100644 index 000000000..f5aba560e --- /dev/null +++ b/mealie/utils/startup.py @@ -0,0 +1,18 @@ +from pathlib import Path + +from settings import REQUIRED_DIRS + +CWD = Path(__file__).parent + + +def pre_start(): + ensure_dirs() + + +def ensure_dirs(): + for dir in REQUIRED_DIRS: + dir.mkdir(parents=True, exist_ok=True) + + +if __name__ == "__main__": + pass diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..1b01426ef --- /dev/null +++ b/poetry.lock @@ -0,0 +1,80 @@ +[[package]] +name = "fastapi" +version = "0.63.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pydantic = ">=1.0.0,<2.0.0" +starlette = "0.13.6" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] +dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] +test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.790)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] + +[[package]] +name = "pydantic" +version = "1.7.3" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] +typing_extensions = ["typing-extensions (>=3.7.2)"] + +[[package]] +name = "starlette" +version = "0.13.6" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "fd85f1208985afed8ec0a1322873546cc1c1eb44f0f443225647806e9f6eead0" + +[metadata.files] +fastapi = [ + {file = "fastapi-0.63.0-py3-none-any.whl", hash = "sha256:98d8ea9591d8512fdadf255d2a8fa56515cdd8624dca4af369da73727409508e"}, + {file = "fastapi-0.63.0.tar.gz", hash = "sha256:63c4592f5ef3edf30afa9a44fa7c6b7ccb20e0d3f68cd9eba07b44d552058dcb"}, +] +pydantic = [ + {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, + {file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"}, + {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127"}, + {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e"}, + {file = "pydantic-1.7.3-cp36-cp36m-win_amd64.whl", hash = "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23"}, + {file = "pydantic-1.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f"}, + {file = "pydantic-1.7.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1"}, + {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c"}, + {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f"}, + {file = "pydantic-1.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2"}, + {file = "pydantic-1.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef"}, + {file = "pydantic-1.7.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef"}, + {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608"}, + {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec"}, + {file = "pydantic-1.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e"}, + {file = "pydantic-1.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e"}, + {file = "pydantic-1.7.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730"}, + {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95"}, + {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b"}, + {file = "pydantic-1.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af"}, + {file = "pydantic-1.7.3-py3-none-any.whl", hash = "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229"}, + {file = "pydantic-1.7.3.tar.gz", hash = "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9"}, +] +starlette = [ + {file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"}, + {file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"}, +] diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 000000000..ab1033bd3 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..c827768fb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,116 @@ +[tool.poetry] +name = "mealie" +version = "0.1.0" +description = "Recipe Manager" +authors = ["Hayden "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.8" +fastapi = "0.61.1" +aiofiles = "0.5.0" +aniso8601 = "7.0.0" +appdirs = "1.4.4" +APScheduler = "3.6.3" +astroid = "2.4.2" +async-exit-stack = "1.0.1" +async_generator = "1.10" +attrs = "20.3.0" +beautifulsoup4 = "4.9.1" +black = "20.8b1" +certifi = "2020.6.20" +chardet = "3.0.4" +click = "7.1.2" +colorama = "0.4.3" +decorator = "4.4.2" +dnspython = "2.0.0" +email-validator = "1.1.1" +extruct = "0.10.0" +fastapi-login = "1.5.1" +future = "0.18.2" +gitdb = "4.0.5" +GitPython = "3.1.11" +graphene = "2.1.8" +graphql-core = "2.3.2" +graphql-relay = "2.0.1" +h11 = "0.9.0" +html-text = "0.5.2" +html5lib = "1.1" +httptools = "0.1.1" +idna = "2.10" +iniconfig = "1.1.1" +isodate = "0.6.0" +isort = "5.4.2" +itsdangerous = "1.1.0" +Jinja2 = "2.11.2" +joblib = "1.0.0" +jstyleson = "0.0.2" +lazy-object-proxy = "1.4.3" +livereload = "2.6.3" +lunr = "0.5.8" +lxml = "4.6.2" +Markdown = "3.3.3" +MarkupSafe = "1.1.1" +mccabe = "0.6.1" +mf2py = "1.1.2" +mkdocs = "1.1.2" +mkdocs-material = "6.1.7" +mkdocs-material-extensions = "1.0.1" +mongoengine = "0.21.0" +mypy-extensions = "0.4.3" +nltk = "3.5" +packaging = "20.8" +passlib = "1.7.4" +pathspec = "0.8.0" +pdfkit = "0.6.1" +pip-chill = "1.0.0" +pluggy = "0.13.1" +promise = "2.3" +py = "1.10.0" +pydantic = "1.6.1" +Pygments = "2.7.3" +PyJWT = "1.7.1" +pylint = "2.6.0" +pymdown-extensions = "8.0.1" +pymongo = "3.11.1" +pyparsing = "2.4.7" +pytest = "6.2.1" +python-dateutil = "2.8.1" +python-dotenv = "0.15.0" +python-multipart = "0.0.5" +python-slugify = "4.0.1" +pytz = "2020.4" +PyYAML = "5.3.1" +rdflib = "4.2.2" +rdflib-jsonld = "0.5.0" +regex = "2020.7.14" +requests = "2.24.0" +Rx = "1.6.1" +scrape-schema-recipe = "0.1.1" +six = "1.15.0" +smmap = "3.0.4" +soupsieve = "2.0.1" +SQLAlchemy = "1.3.22" +starlette = "0.13.6" +text-unidecode = "1.3" +toml = "0.10.1" +tornado = "6.1" +tqdm = "4.54.1" +typed-ast = "1.4.1" +typing-extensions = "3.7.4.3" +tzlocal = "2.1" +ujson = "3.1.0" +urllib3 = "1.25.10" +uvicorn = "0.11.8" +uvloop = "0.14.0" +validators = "0.18.0" +w3lib = "1.22.0" +webencodings = "0.5.1" +websockets = "8.1" +wrapt = "1.12.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"