mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	Get Recipes Route Rewrite (#339)
* deprecate old route * auto-gen * recipe card infinite scroll * fix datatable * set hard-limit option * add loader * set scroll on navigation * add auto-import * fix slow initial load * remove console.logs Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
		
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,7 +2,6 @@ import { baseURL } from "./api-utils"; | |||||||
| import { apiReq } from "./api-utils"; | import { apiReq } from "./api-utils"; | ||||||
| import { store } from "../store"; | import { store } from "../store"; | ||||||
| import { router } from "../main"; | import { router } from "../main"; | ||||||
| import qs from "qs"; |  | ||||||
|  |  | ||||||
| const prefix = baseURL + "recipes/"; | const prefix = baseURL + "recipes/"; | ||||||
|  |  | ||||||
| @@ -78,22 +77,10 @@ export const recipeAPI = { | |||||||
|     router.push(`/`); |     router.push(`/`); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   async allByKeys(recipeKeys, num = 9999) { |   async allSummary(start = 0, limit = 9999) { | ||||||
|     const response = await apiReq.get(recipeURLs.allRecipes, { |     const response = await apiReq.get(recipeURLs.summary, { | ||||||
|       params: { |       params: { start: start, limit: limit }, | ||||||
|         keys: recipeKeys, |  | ||||||
|         num: num, |  | ||||||
|       }, |  | ||||||
|       paramsSerializer: params => { |  | ||||||
|         return qs.stringify(params, { arrayFormat: "repeat" }); |  | ||||||
|       }, |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     return response.data; |  | ||||||
|   }, |  | ||||||
|  |  | ||||||
|   async allSummary() { |  | ||||||
|     const response = await apiReq.get(recipeURLs.summary); |  | ||||||
|     return response.data; |     return response.data; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import DataTable from "@/components/ImportSummaryDialog"; | import DataTable from "@/components/ImportSummaryDialog/DataTable"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     DataTable, |     DataTable, | ||||||
|   | |||||||
| @@ -116,6 +116,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   async mounted() { |   async mounted() { | ||||||
|     await this.$store.dispatch("requestCurrentGroup"); |     await this.$store.dispatch("requestCurrentGroup"); | ||||||
|  |     await this.$store.dispatch("requestAllRecipes"); | ||||||
|     await this.buildMealStore(); |     await this.buildMealStore(); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
| @@ -151,6 +152,9 @@ export default { | |||||||
|       const recipes = this.items.filter(x => !this.usedRecipes.includes(x)); |       const recipes = this.items.filter(x => !this.usedRecipes.includes(x)); | ||||||
|       return recipes.length > 0 ? recipes : this.items; |       return recipes.length > 0 ? recipes : this.items; | ||||||
|     }, |     }, | ||||||
|  |     allRecipes() { | ||||||
|  |       return this.$store.getters.getRecentRecipes; | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   methods: { |   methods: { | ||||||
| @@ -159,15 +163,7 @@ export default { | |||||||
|       this.items = await api.recipes.getAllByCategory(categories); |       this.items = await api.recipes.getAllByCategory(categories); | ||||||
|  |  | ||||||
|       if (this.items.length === 0) { |       if (this.items.length === 0) { | ||||||
|         const keys = [ |         this.items = this.allRecipes; | ||||||
|           "name", |  | ||||||
|           "slug", |  | ||||||
|           "image", |  | ||||||
|           "description", |  | ||||||
|           "dateAdded", |  | ||||||
|           "rating", |  | ||||||
|         ]; |  | ||||||
|         this.items = await api.recipes.allByKeys(keys); |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     getRandom(list) { |     getRandom(list) { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
|     > |     > | ||||||
|       <ConfirmationDialog |       <ConfirmationDialog | ||||||
|         :title="$t('recipe.delete-recipe')" |         :title="$t('recipe.delete-recipe')" | ||||||
|         :message="$t('recipe.delete-ConfirmationDialog')" |         :message="$t('recipe.delete-confirmation')" | ||||||
|         color="error" |         color="error" | ||||||
|         icon="mdi-alert-circle" |         icon="mdi-alert-circle" | ||||||
|         ref="deleteRecipieConfirm" |         ref="deleteRecipieConfirm" | ||||||
|   | |||||||
| @@ -33,11 +33,6 @@ export default { | |||||||
|     totalTime: String, |     totalTime: String, | ||||||
|     performTime: String, |     performTime: String, | ||||||
|   }, |   }, | ||||||
|   watch: { |  | ||||||
|     showCards(val) { |  | ||||||
|       console.log(val); |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   computed: { |   computed: { | ||||||
|     showCards() { |     showCards() { | ||||||
|       return [this.prepTime, this.totalTime, this.performTime].some( |       return [this.prepTime, this.totalTime, this.performTime].some( | ||||||
|   | |||||||
| @@ -78,6 +78,16 @@ | |||||||
|         </v-col> |         </v-col> | ||||||
|       </v-row> |       </v-row> | ||||||
|     </div> |     </div> | ||||||
|  |     <div v-intersect="bumpList" class="d-flex"> | ||||||
|  |       <v-progress-circular | ||||||
|  |         v-if="loading" | ||||||
|  |         class="mx-auto mt-1" | ||||||
|  |         :size="50" | ||||||
|  |         :width="7" | ||||||
|  |         color="primary" | ||||||
|  |         indeterminate | ||||||
|  |       ></v-progress-circular> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -96,10 +106,16 @@ export default { | |||||||
|     title: { |     title: { | ||||||
|       default: null, |       default: null, | ||||||
|     }, |     }, | ||||||
|     recipes: Array, |     hardLimit: { | ||||||
|     cardLimit: { |       default: 99999, | ||||||
|       default: 999, |  | ||||||
|     }, |     }, | ||||||
|  |     recipes: Array, | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       cardLimit: 30, | ||||||
|  |       loading: false, | ||||||
|  |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     viewScale() { |     viewScale() { | ||||||
| @@ -113,6 +129,22 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|  |   methods: { | ||||||
|  |     bumpList() { | ||||||
|  |       const newCardLimit = Math.min(this.cardLimit + 20, this.hardLimit); | ||||||
|  |  | ||||||
|  |       if (this.loading === false && newCardLimit > this.cardLimit) { | ||||||
|  |         this.setLoader(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       this.cardLimit = newCardLimit; | ||||||
|  |     }, | ||||||
|  |     async setLoader() { | ||||||
|  |       this.loading = true; | ||||||
|  |       await new Promise(r => setTimeout(r, 3000)); | ||||||
|  |       this.loading = false; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -89,7 +89,6 @@ export default { | |||||||
|       searchSlug: "", |       searchSlug: "", | ||||||
|       search: "", |       search: "", | ||||||
|       menuModel: false, |       menuModel: false, | ||||||
|       data: [], |  | ||||||
|       result: [], |       result: [], | ||||||
|       fuseResults: [], |       fuseResults: [], | ||||||
|       isDark: false, |       isDark: false, | ||||||
| @@ -107,9 +106,12 @@ export default { | |||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.isDark = this.$store.getters.getIsDark; |     this.isDark = this.$store.getters.getIsDark; | ||||||
|     this.data = this.$store.getters.getRecentRecipes; |     this.$store.dispatch("requestAllRecipes"); | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  |     data() { | ||||||
|  |       return this.$store.getters.getRecentRecipes; | ||||||
|  |     }, | ||||||
|     autoResults() { |     autoResults() { | ||||||
|       return this.fuseResults.length > 1 ? this.fuseResults : this.results; |       return this.fuseResults.length > 1 ? this.fuseResults : this.results; | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -135,7 +135,6 @@ export default { | |||||||
|       this.groupSettings.webhookUrls.splice(index, 1); |       this.groupSettings.webhookUrls.splice(index, 1); | ||||||
|     }, |     }, | ||||||
|     async saveGroupSettings() { |     async saveGroupSettings() { | ||||||
|       console.log(this.groupSettings); |  | ||||||
|       await api.groups.update(this.groupSettings); |       await api.groups.update(this.groupSettings); | ||||||
|       await this.$store.dispatch("requestCurrentGroup"); |       await this.$store.dispatch("requestCurrentGroup"); | ||||||
|       this.getSiteSettings(); |       this.getSiteSettings(); | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import DataTable from "@/components/ImportSummaryDialog"; | import DataTable from "@/components/ImportSummaryDialog/DataTable"; | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     DataTable, |     DataTable, | ||||||
|   | |||||||
| @@ -223,8 +223,7 @@ export default { | |||||||
|       this.settings.categories.splice(index, 1); |       this.settings.categories.splice(index, 1); | ||||||
|     }, |     }, | ||||||
|     async saveSettings() { |     async saveSettings() { | ||||||
|       const newSettings = await api.siteSettings.update(this.settings); |       await api.siteSettings.update(this.settings); | ||||||
|       console.log("New Settings", newSettings); |  | ||||||
|       this.getOptions(); |       this.getOptions(); | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|       v-if="siteSettings.showRecent" |       v-if="siteSettings.showRecent" | ||||||
|       :title="$t('page.recent')" |       :title="$t('page.recent')" | ||||||
|       :recipes="recentRecipes" |       :recipes="recentRecipes" | ||||||
|       :card-limit="siteSettings.cardsPerSection" |       :hard-limit="siteSettings.cardsPerSection" | ||||||
|     /> |     /> | ||||||
|     <CardSection |     <CardSection | ||||||
|       :sortable="true" |       :sortable="true" | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|       :key="section.name + section.position" |       :key="section.name + section.position" | ||||||
|       :title="section.name" |       :title="section.name" | ||||||
|       :recipes="section.recipes" |       :recipes="section.recipes" | ||||||
|       :card-limit="siteSettings.cardsPerSection" |       :hard-limit="siteSettings.cardsPerSection" | ||||||
|       @sort="sortAZ(index)" |       @sort="sortAZ(index)" | ||||||
|       @sort-recent="sortRecent(index)" |       @sort-recent="sortRecent(index)" | ||||||
|     /> |     /> | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ | |||||||
|       :sortable="true" |       :sortable="true" | ||||||
|       :title="$t('page.all-recipes')" |       :title="$t('page.all-recipes')" | ||||||
|       :recipes="allRecipes" |       :recipes="allRecipes" | ||||||
|       :card-limit="9999" |  | ||||||
|       @sort="sortAZ" |       @sort="sortAZ" | ||||||
|       @sort-recent="sortRecent" |       @sort-recent="sortRecent" | ||||||
|     /> |     /> | ||||||
| @@ -23,6 +22,9 @@ export default { | |||||||
|   data() { |   data() { | ||||||
|     return {}; |     return {}; | ||||||
|   }, |   }, | ||||||
|  |   mounted() { | ||||||
|  |     this.$store.dispatch("requestAllRecipes"); | ||||||
|  |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     allRecipes() { |     allRecipes() { | ||||||
|       return this.$store.getters.getRecentRecipes; |       return this.$store.getters.getRecentRecipes; | ||||||
|   | |||||||
| @@ -26,7 +26,9 @@ | |||||||
|  |  | ||||||
|       <v-row dense class="mt-0 flex-row align-center justify-space-around"> |       <v-row dense class="mt-0 flex-row align-center justify-space-around"> | ||||||
|         <v-col> |         <v-col> | ||||||
|           <h3 class="pl-2 text-center headline">{{$t('search.category-filter')}}</h3> |           <h3 class="pl-2 text-center headline"> | ||||||
|  |             {{ $t("search.category-filter") }} | ||||||
|  |           </h3> | ||||||
|           <FilterSelector class="mb-1" @update="updateCatParams" /> |           <FilterSelector class="mb-1" @update="updateCatParams" /> | ||||||
|           <CategoryTagSelector |           <CategoryTagSelector | ||||||
|             :solo="true" |             :solo="true" | ||||||
| @@ -36,7 +38,9 @@ | |||||||
|           /> |           /> | ||||||
|         </v-col> |         </v-col> | ||||||
|         <v-col> |         <v-col> | ||||||
|           <h3 class="pl-2 text-center headline">{{$t('search.tag-filter')}}</h3> |           <h3 class="pl-2 text-center headline"> | ||||||
|  |             {{ $t("search.tag-filter") }} | ||||||
|  |           </h3> | ||||||
|           <FilterSelector class="mb-1" @update="updateTagParams" /> |           <FilterSelector class="mb-1" @update="updateTagParams" /> | ||||||
|  |  | ||||||
|           <CategoryTagSelector |           <CategoryTagSelector | ||||||
| @@ -113,6 +117,9 @@ export default { | |||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  |   mounted() { | ||||||
|  |     this.$store.dispatch("requestAllRecipes"); | ||||||
|  |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     allRecipes() { |     allRecipes() { | ||||||
|       return this.$store.getters.getRecentRecipes; |       return this.$store.getters.getRecentRecipes; | ||||||
|   | |||||||
| @@ -24,6 +24,9 @@ export const routes = [ | |||||||
| const router = new VueRouter({ | const router = new VueRouter({ | ||||||
|   routes, |   routes, | ||||||
|   mode: process.env.NODE_ENV === "production" ? "history" : "hash", |   mode: process.env.NODE_ENV === "production" ? "history" : "hash", | ||||||
|  |   scrollBehavior() { | ||||||
|  |     return { x: 0, y: 0 }; | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const DEFAULT_TITLE = "Mealie"; | const DEFAULT_TITLE = "Mealie"; | ||||||
|   | |||||||
| @@ -53,19 +53,18 @@ const store = new Vuex.Store({ | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   actions: { |   actions: { | ||||||
|     async requestRecentRecipes() { |     async requestRecentRecipes({ getters }) { | ||||||
|       // const keys = [ |       const payload = await api.recipes.allSummary(0, 30); | ||||||
|       //   "name", |       const recent = getters.getRecentRecipes; | ||||||
|       //   "slug", |       if (recent.length >= 30) return; | ||||||
|       //   "image", |  | ||||||
|       //   "description", |  | ||||||
|       //   "dateAdded", |  | ||||||
|       //   "rating", |  | ||||||
|       // ]; |  | ||||||
|       const payload = await api.recipes.allSummary(); |  | ||||||
|  |  | ||||||
|       this.commit("setRecentRecipes", payload); |       this.commit("setRecentRecipes", payload); | ||||||
|     }, |     }, | ||||||
|  |     async requestAllRecipes({ getters }) { | ||||||
|  |       const recent = getters.getRecentRecipes; | ||||||
|  |       const start = recent.length + 1; | ||||||
|  |       const payload = await api.recipes.allSummary(start, 9999); | ||||||
|  |       this.commit("setRecentRecipes", [...recent, ...payload]); | ||||||
|  |     }, | ||||||
|     async requestCategories({ commit }) { |     async requestCategories({ commit }) { | ||||||
|       const categories = await api.categories.getAll(); |       const categories = await api.categories.getAll(); | ||||||
|       commit("setAllCategories", categories); |       commit("setAllCategories", categories); | ||||||
| @@ -74,7 +73,6 @@ const store = new Vuex.Store({ | |||||||
|       const tags = await api.tags.getAll(); |       const tags = await api.tags.getAll(); | ||||||
|       commit("setAllTags", tags); |       commit("setAllTags", tags); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     async requestAppInfo({ commit }) { |     async requestAppInfo({ commit }) { | ||||||
|       const response = await api.meta.getAppInfo(); |       const response = await api.meta.getAppInfo(); | ||||||
|       commit("setAppInfo", response); |       commit("setAppInfo", response); | ||||||
|   | |||||||
| @@ -16,10 +16,12 @@ class BaseDocument: | |||||||
|         self.schema: BaseModel |         self.schema: BaseModel | ||||||
|  |  | ||||||
|     # TODO: Improve Get All Query Functionality |     # TODO: Improve Get All Query Functionality | ||||||
|     def get_all(self, session: Session, limit: int = None, order_by: str = None, override_schema=None) -> List[dict]: |     def get_all( | ||||||
|  |         self, session: Session, limit: int = None, order_by: str = None, start=0, end=9999, override_schema=None | ||||||
|  |     ) -> List[dict]: | ||||||
|         eff_schema = override_schema or self.schema |         eff_schema = override_schema or self.schema | ||||||
|  |  | ||||||
|         return [eff_schema.from_orm(x) for x in session.query(self.sql_model).limit(limit).all()] |         return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()] | ||||||
|  |  | ||||||
|     def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]: |     def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]: | ||||||
|         """Queries the database for the selected model. Restricts return responses to the |         """Queries the database for the selected model. Restricts return responses to the | ||||||
|   | |||||||
| @@ -12,16 +12,26 @@ router = APIRouter(tags=["Query All Recipes"]) | |||||||
|  |  | ||||||
| @router.get("/api/recipes/summary") | @router.get("/api/recipes/summary") | ||||||
| async def get_recipe_summary( | async def get_recipe_summary( | ||||||
|     skip=0, |     start=0, | ||||||
|     end=9999, |     limit=9999, | ||||||
|     session: Session = Depends(generate_session), |     session: Session = Depends(generate_session), | ||||||
| ): | ): | ||||||
|     """ Returns the summary data for recipes in the database """ |     """ | ||||||
|  |     Returns key the recipe summary data for recipes in the database. You can perform | ||||||
|  |     slice operations to set the skip/end amounts for recipes. All recipes are sorted by the added date. | ||||||
|  |  | ||||||
|     return db.recipes.get_all(session, limit=end, override_schema=RecipeSummary) |     **Query Parameters** | ||||||
|  |     - skip: The database entry to start at. (0 Indexed) | ||||||
|  |     - end: The number of entries to return. | ||||||
|  |  | ||||||
|  |     skip=2, end=10 will return entries | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     return db.recipes.get_all(session, limit=limit, start=start, override_schema=RecipeSummary) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/api/recipes") | @router.get("/api/recipes", deprecated=True) | ||||||
| def get_all_recipes( | def get_all_recipes( | ||||||
|     keys: Optional[List[str]] = Query(...), |     keys: Optional[List[str]] = Query(...), | ||||||
|     num: Optional[int] = 100, |     num: Optional[int] = 100, | ||||||
| @@ -54,7 +64,7 @@ def get_all_recipes( | |||||||
|     return db.recipes.get_all_limit_columns(session, keys, limit=num) |     return db.recipes.get_all_limit_columns(session, keys, limit=num) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/api/recipes") | @router.post("/api/recipes", deprecated=True) | ||||||
| def get_all_recipes_post(body: AllRecipeRequest, session: Session = Depends(generate_session)): | def get_all_recipes_post(body: AllRecipeRequest, session: Session = Depends(generate_session)): | ||||||
|     """ |     """ | ||||||
|     Returns key data for all recipes based off the body data provided. |     Returns key data for all recipes based off the body data provided. | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								template.env
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								template.env
									
									
									
									
									
								
							| @@ -1,11 +1,34 @@ | |||||||
| # Make .env in this folder if needed. | # The Default Group Assigned to All Users | ||||||
| DEFAULT_GROUP=Home | DEFAULT_GROUP=Home | ||||||
| PRODUCTION=False |  | ||||||
| API_PORT=9000 | # The Default Credentials for the Super User | ||||||
| API_DOCS=True |  | ||||||
| DB_TYPE=sqlite |  | ||||||
| DEFAULT_EMAIL=changeme@email.com | DEFAULT_EMAIL=changeme@email.com | ||||||
| DEFAULT_PASSWORD=MyPassword | DEFAULT_PASSWORD=MyPassword | ||||||
| TOKEN_TIME=2 |  | ||||||
|  | # Determines Production Mode, This will set the directory path to use for data storage | ||||||
|  | PRODUCTION=False | ||||||
|  |  | ||||||
|  | # API Port for Pythong Server | ||||||
|  | API_PORT=9000 | ||||||
|  |  | ||||||
|  | # Exposes /docs and /redoc on the server | ||||||
|  | API_DOCS=True | ||||||
|  |  | ||||||
|  | # Sets the Database type to use. Currently the only supported options is 'sqlite' | ||||||
|  | DB_TYPE=sqlite | ||||||
|  |  | ||||||
|  | # Sets the token expiration time in hours.  | ||||||
|  | TOKEN_TIME=24 | ||||||
|  |  | ||||||
|  | # NOT USED | ||||||
| SFTP_USERNAME=None | SFTP_USERNAME=None | ||||||
| SFTP_PASSWORD=None | SFTP_PASSWORD=None | ||||||
|  |  | ||||||
|  | # NOT USED Auto Import Options  | ||||||
|  | AUTO_IMPORT=True | ||||||
|  | AUTO_IMPORT_RECIPES=True | ||||||
|  | AUTO_IMPORT_SETTINGS=True | ||||||
|  | AUTO_IMPORT_PAGES=True | ||||||
|  | AUTO_IMPORT_THEMES=True | ||||||
|  | AUTO_IMPORT_USERS=True | ||||||
|  | AUTO_IMPORT_GROUPS=True | ||||||
		Reference in New Issue
	
	Block a user