mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	imrpove api docs
This commit is contained in:
		
							
								
								
									
										20
									
								
								.github/workflows/build-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/build-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | name: Publish docs via GitHub Pages | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     name: Deploy docs | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout main | ||||||
|  |         uses: actions/checkout@v1 | ||||||
|  |  | ||||||
|  |       - name: Deploy docs | ||||||
|  |         uses: mhausenblas/mkdocs-deploy-gh-pages@master | ||||||
|  |         env: | ||||||
|  |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |           CONFIG_FILE: docs/mkdocs.yml | ||||||
|  |           EXTRA_PACKAGES: build-base | ||||||
							
								
								
									
										5
									
								
								docs/docs/api/api-examples.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/docs/api/api-examples.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | # API Examples | ||||||
|  |  | ||||||
|  | TODO | ||||||
|  |  | ||||||
|  | Have Ideas? Submit a PR! | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| # API Introduction |  | ||||||
|  |  | ||||||
| TODO |  | ||||||
							
								
								
									
										26
									
								
								docs/docs/api/demo/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								docs/docs/api/demo/index.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										26
									
								
								docs/docs/html/api.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								docs/docs/html/api.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -7,7 +7,6 @@ theme: | |||||||
|     logo: material/silverware-variant |     logo: material/silverware-variant | ||||||
|   features: |   features: | ||||||
|     - navigation.expand |     - navigation.expand | ||||||
|     - navigation.instant |  | ||||||
|  |  | ||||||
| markdown_extensions: | markdown_extensions: | ||||||
|   - pymdownx.emoji: |   - pymdownx.emoji: | ||||||
| @@ -35,7 +34,8 @@ nav: | |||||||
|       - Backups and Exports: "getting-started/backups-and-exports.md" |       - Backups and Exports: "getting-started/backups-and-exports.md" | ||||||
|       - Recipe Migration: "getting-started/migration-imports.md" |       - Recipe Migration: "getting-started/migration-imports.md" | ||||||
|   - API Reference: |   - API Reference: | ||||||
|       - Swagger/OpenAPI: "api/api-intro.md" |       - API Documentation: "api/demo/index.html" | ||||||
|  |       - Usage Examples: "api/api-examples.md" | ||||||
|   - Contributors Guide: |   - Contributors Guide: | ||||||
|       - Non-Code: "contributors/non-coders.md" |       - Non-Code: "contributors/non-coders.md" | ||||||
|       - Developers Guide: |       - Developers Guide: | ||||||
|   | |||||||
							
								
								
									
										198
									
								
								frontend/src/components/RecipeEditor/PrintRecipe.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								frontend/src/components/RecipeEditor/PrintRecipe.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | |||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <v-card flat class="d-print-none"> | ||||||
|  |       <v-card-text> | ||||||
|  |         <v-row align="center" justify="center"> | ||||||
|  |           <v-btn | ||||||
|  |             left | ||||||
|  |             color="accent lighten-1 " | ||||||
|  |             class="ma-1 image-action" | ||||||
|  |             @click="$emit('exit')" | ||||||
|  |           > | ||||||
|  |             <v-icon> mdi-arrow-left </v-icon> | ||||||
|  |           </v-btn> | ||||||
|  |           <v-card flat class="text-center" align-center> | ||||||
|  |             <v-card-text>Font Size</v-card-text> | ||||||
|  |             <v-card-text> | ||||||
|  |               <v-btn | ||||||
|  |                 class="mx-2" | ||||||
|  |                 fab | ||||||
|  |                 dark | ||||||
|  |                 x-small | ||||||
|  |                 color="primary" | ||||||
|  |                 @click="subtractFontSize" | ||||||
|  |               > | ||||||
|  |                 <v-icon dark> mdi-minus </v-icon> | ||||||
|  |               </v-btn> | ||||||
|  |               <v-btn | ||||||
|  |                 class="mx-2" | ||||||
|  |                 fab | ||||||
|  |                 dark | ||||||
|  |                 x-small | ||||||
|  |                 color="primary" | ||||||
|  |                 @click="addFontSize" | ||||||
|  |               > | ||||||
|  |                 <v-icon dark> mdi-plus </v-icon> | ||||||
|  |               </v-btn> | ||||||
|  |             </v-card-text> | ||||||
|  |           </v-card> | ||||||
|  |         </v-row> | ||||||
|  |       </v-card-text> | ||||||
|  |     </v-card> | ||||||
|  |  | ||||||
|  |     <v-card flat> | ||||||
|  |       <v-row dense align="center"> | ||||||
|  |         <v-col md="10" sm="10"> | ||||||
|  |           <v-card flat> | ||||||
|  |             <v-card-title> {{ recipe.name }} </v-card-title> | ||||||
|  |  | ||||||
|  |             <v-card-text> {{ recipe.description }} </v-card-text> | ||||||
|  |  | ||||||
|  |             <v-divider></v-divider> | ||||||
|  |           </v-card> | ||||||
|  |         </v-col> | ||||||
|  |         <v-col md="1" sm="1" justify-end> | ||||||
|  |           <v-img :src="getImage(recipe.image)" max-height="200" max-width="300"> | ||||||
|  |           </v-img> | ||||||
|  |         </v-col> | ||||||
|  |       </v-row> | ||||||
|  |     </v-card> | ||||||
|  |     <v-card flat align> | ||||||
|  |       <v-card-text> | ||||||
|  |         <v-row class="mt-n6"> | ||||||
|  |           <v-col> | ||||||
|  |             <v-btn | ||||||
|  |               v-if="recipe.recipeYield" | ||||||
|  |               dense | ||||||
|  |               small | ||||||
|  |               :hover="false" | ||||||
|  |               type="label" | ||||||
|  |               :ripple="false" | ||||||
|  |               elevation="0" | ||||||
|  |               color="secondary darken-1" | ||||||
|  |               class="rounded-sm static" | ||||||
|  |             > | ||||||
|  |               {{ recipe.recipeYield }} | ||||||
|  |             </v-btn> | ||||||
|  |           </v-col> | ||||||
|  |           <v-rating | ||||||
|  |             class="mr-2 align-end static" | ||||||
|  |             color="secondary darken-1" | ||||||
|  |             background-color="secondary lighten-3" | ||||||
|  |             length="5" | ||||||
|  |             :value="recipe.rating" | ||||||
|  |           ></v-rating> | ||||||
|  |         </v-row> | ||||||
|  |         <h2 class="mt-1">Ingredients</h2> | ||||||
|  |         <v-row> | ||||||
|  |           <v-list dense class="column-wrapper align-start"> | ||||||
|  |             <v-list-item | ||||||
|  |               v-for="(ingredient, index) in recipe.recipeIngredient" | ||||||
|  |               :key="generateKey('ingredient', index)" | ||||||
|  |               hide-details | ||||||
|  |               class="mb-n3 print-text" | ||||||
|  |               :label="ingredient" | ||||||
|  |             > | ||||||
|  |               <v-list-item-icon class="mr-1"> | ||||||
|  |                 <v-icon> mdi-minus </v-icon> | ||||||
|  |               </v-list-item-icon> | ||||||
|  |               {{ ingredient }} | ||||||
|  |             </v-list-item> | ||||||
|  |           </v-list> | ||||||
|  |         </v-row> | ||||||
|  |         <v-row dense> | ||||||
|  |           <v-col cols="12"> | ||||||
|  |             <div v-if="recipe.categories[0]"> | ||||||
|  |               <h2 class="mt-4">Categories</h2> | ||||||
|  |               <v-chip | ||||||
|  |                 class="ma-1" | ||||||
|  |                 color="primary" | ||||||
|  |                 dark | ||||||
|  |                 v-for="category in recipe.categories" | ||||||
|  |                 :key="category" | ||||||
|  |               > | ||||||
|  |                 {{ category }} | ||||||
|  |               </v-chip> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div v-if="recipe.tags[0]"> | ||||||
|  |               <h2 class="mt-4">Tags</h2> | ||||||
|  |               <v-chip | ||||||
|  |                 class="ma-1" | ||||||
|  |                 color="primary" | ||||||
|  |                 dark | ||||||
|  |                 v-for="tag in recipe.tags" | ||||||
|  |                 :key="tag" | ||||||
|  |               > | ||||||
|  |                 {{ tag }} | ||||||
|  |               </v-chip> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <h2 v-if="recipe.notes[0]" class="my-2">Notes</h2> | ||||||
|  |             <v-card | ||||||
|  |               flat | ||||||
|  |               class="mt-1" | ||||||
|  |               v-for="(note, index) in recipe.notes" | ||||||
|  |               :key="generateKey('note', index)" | ||||||
|  |             > | ||||||
|  |               <v-card-title> {{ note.title }}</v-card-title> | ||||||
|  |               <v-card-text> | ||||||
|  |                 {{ note.text }} | ||||||
|  |               </v-card-text> | ||||||
|  |             </v-card> | ||||||
|  |           </v-col> | ||||||
|  |           <v-col cols="12"> | ||||||
|  |             <h2 class="mb-4">Instructions</h2> | ||||||
|  |  | ||||||
|  |             <v-card | ||||||
|  |               v-for="(step, index) in recipe.recipeInstructions" | ||||||
|  |               :key="generateKey('step', index)" | ||||||
|  |               class="my-n4" | ||||||
|  |               flat | ||||||
|  |             > | ||||||
|  |               <v-card-title class="my-n4">Step: {{ index + 1 }}</v-card-title> | ||||||
|  |               <v-card-text class="my-n4">{{ step.text }}</v-card-text> | ||||||
|  |             </v-card> | ||||||
|  |           </v-col> | ||||||
|  |         </v-row> | ||||||
|  |       </v-card-text> | ||||||
|  |     </v-card> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import utils from "../../utils"; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     recipe: Object, | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       fontSize: 1.0, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     getImage(image) { | ||||||
|  |       if (image) { | ||||||
|  |         return utils.getImageURL(image) + "?rnd=" + this.imageKey; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     generateKey(item, index) { | ||||||
|  |       return utils.generateUniqueKey(item, index); | ||||||
|  |     }, | ||||||
|  |     addFontSize() { | ||||||
|  |       this.fontSize += 0.2; | ||||||
|  |     }, | ||||||
|  |     subtractFontSize() { | ||||||
|  |       this.fontSize -= 0.2; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  | .column-wrapper { | ||||||
|  |   column-count: 2; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -1,5 +1,4 @@ | |||||||
| from pathlib import Path | from pathlib import Path | ||||||
| import os |  | ||||||
|  |  | ||||||
| import uvicorn | import uvicorn | ||||||
| from fastapi import FastAPI | from fastapi import FastAPI | ||||||
| @@ -15,8 +14,8 @@ from routes import ( | |||||||
|     static_routes, |     static_routes, | ||||||
|     user_routes, |     user_routes, | ||||||
| ) | ) | ||||||
| from routes.setting_routes import scheduler | from routes.setting_routes import scheduler  # ! This has to be imported for scheduling | ||||||
| from settings import PORT | from settings import PORT, PRODUCTION | ||||||
| from utils.logger import logger | from utils.logger import logger | ||||||
|  |  | ||||||
| CWD = Path(__file__).parent | CWD = Path(__file__).parent | ||||||
| @@ -26,10 +25,10 @@ app = FastAPI() | |||||||
|  |  | ||||||
|  |  | ||||||
| # Mount Vue Frontend only in production | # Mount Vue Frontend only in production | ||||||
| env = os.environ.get("ENV") | if PRODUCTION: | ||||||
| if(env == "prod"): |  | ||||||
|     app.mount("/static", StaticFiles(directory=WEB_PATH, html=True)) |     app.mount("/static", StaticFiles(directory=WEB_PATH, html=True)) | ||||||
|  |  | ||||||
|  |  | ||||||
| # API Routes | # API Routes | ||||||
| app.include_router(recipe_routes.router) | app.include_router(recipe_routes.router) | ||||||
| app.include_router(meal_routes.router) | app.include_router(meal_routes.router) | ||||||
| @@ -49,6 +48,9 @@ app.include_router(static_routes.router) | |||||||
| startup.ensure_dirs() | startup.ensure_dirs() | ||||||
| startup.generate_default_theme() | startup.generate_default_theme() | ||||||
|  |  | ||||||
|  | # Generate API Documentation | ||||||
|  | if not PRODUCTION: | ||||||
|  |     startup.generate_api_docs(app) | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     logger.info("-----SYSTEM STARTUP-----") |     logger.info("-----SYSTEM STARTUP-----") | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| # from datetime import datetime | # from datetime import datetime | ||||||
| from typing import Optional | from typing import List, Optional | ||||||
|  |  | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||||
|  |  | ||||||
| @@ -7,3 +7,24 @@ from pydantic import BaseModel | |||||||
| class BackupJob(BaseModel): | class BackupJob(BaseModel): | ||||||
|     tag: Optional[str] |     tag: Optional[str] | ||||||
|     template: Optional[str] |     template: Optional[str] | ||||||
|  |  | ||||||
|  |     class Config: | ||||||
|  |         schema_extra = { | ||||||
|  |             "example": { | ||||||
|  |                 "tag": "July 23rd 2021", | ||||||
|  |                 "template": "recipes.md", | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Imports(BaseModel): | ||||||
|  |     imports: List[str] | ||||||
|  |     templates: List[str] | ||||||
|  |  | ||||||
|  |     class Config: | ||||||
|  |         schema_extra = { | ||||||
|  |             "example": { | ||||||
|  |                 "imports": ["sample_data.zip", "sampe_data2.zip"], | ||||||
|  |                 "templates": ["recipes.md", "custom_template.md"], | ||||||
|  |             } | ||||||
|  |         } | ||||||
|   | |||||||
| @@ -1,14 +1,20 @@ | |||||||
| from fastapi import APIRouter, HTTPException | from fastapi import APIRouter, HTTPException | ||||||
| from models.backup_models import BackupJob | from models.backup_models import BackupJob, Imports | ||||||
| from services.backup_services import (BACKUP_DIR, TEMPLATE_DIR, export_db, | from pydantic.main import BaseModel | ||||||
|                                       import_from_archive) | from services.backup_services import ( | ||||||
|  |     BACKUP_DIR, | ||||||
|  |     TEMPLATE_DIR, | ||||||
|  |     export_db, | ||||||
|  |     import_from_archive, | ||||||
|  | ) | ||||||
| from utils.snackbar import SnackResponse | from utils.snackbar import SnackResponse | ||||||
|  |  | ||||||
| router = APIRouter() | router = APIRouter() | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.get("/api/backups/available/", tags=["Import / Export"]) | @router.get("/api/backups/available/", tags=["Import / Export"], response_model=Imports) | ||||||
| async def available_imports(): | async def available_imports(): | ||||||
|  |     """Returns a list of avaiable .zip files for import into Mealie.""" | ||||||
|     imports = [] |     imports = [] | ||||||
|     templates = [] |     templates = [] | ||||||
|     for archive in BACKUP_DIR.glob("*.zip"): |     for archive in BACKUP_DIR.glob("*.zip"): | ||||||
| @@ -17,12 +23,12 @@ async def available_imports(): | |||||||
|     for template in TEMPLATE_DIR.glob("*.md"): |     for template in TEMPLATE_DIR.glob("*.md"): | ||||||
|         templates.append(template.name) |         templates.append(template.name) | ||||||
|  |  | ||||||
|     return {"imports": imports, "templates": templates} |     return Imports(imports=imports, templates=templates) | ||||||
|  |  | ||||||
|  |  | ||||||
| @router.post("/api/backups/export/database/", tags=["Import / Export"], status_code=201) | @router.post("/api/backups/export/database/", tags=["Import / Export"], status_code=201) | ||||||
| async def export_database(data: BackupJob): | async def export_database(data: BackupJob): | ||||||
|  |     """Generates a backup of the recipe database in json format.""" | ||||||
|     try: |     try: | ||||||
|         export_path = export_db(data.tag, data.template) |         export_path = export_db(data.tag, data.template) | ||||||
|     except: |     except: | ||||||
| @@ -38,6 +44,7 @@ async def export_database(data: BackupJob): | |||||||
|     "/api/backups/{file_name}/import/", tags=["Import / Export"], status_code=200 |     "/api/backups/{file_name}/import/", tags=["Import / Export"], status_code=200 | ||||||
| ) | ) | ||||||
| async def import_database(file_name: str): | async def import_database(file_name: str): | ||||||
|  |     """ Import a database backup file generated from Mealie. """ | ||||||
|     imported = import_from_archive(file_name) |     imported = import_from_archive(file_name) | ||||||
|     return imported |     return imported | ||||||
|  |  | ||||||
| @@ -48,6 +55,7 @@ async def import_database(file_name: str): | |||||||
|     status_code=200, |     status_code=200, | ||||||
| ) | ) | ||||||
| async def delete_backup(backup_name: str): | async def delete_backup(backup_name: str): | ||||||
|  |     """ Removes a database backup from the file system """ | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         BACKUP_DIR.joinpath(backup_name).unlink() |         BACKUP_DIR.joinpath(backup_name).unlink() | ||||||
|   | |||||||
| @@ -16,13 +16,10 @@ async def get_all_meals(): | |||||||
|  |  | ||||||
| @router.post("/api/meal-plan/create/", tags=["Meal Plan"]) | @router.post("/api/meal-plan/create/", tags=["Meal Plan"]) | ||||||
| async def set_meal_plan(data: MealPlan): | async def set_meal_plan(data: MealPlan): | ||||||
|     """ Creates Mealplan from Frontend Data""" |     """ Creates a mealplan database entry""" | ||||||
|     data.process_meals() |     data.process_meals() | ||||||
|     data.save_to_db() |     data.save_to_db() | ||||||
|  |  | ||||||
|     # try: |  | ||||||
|  |  | ||||||
|     # except: |  | ||||||
|     #     raise HTTPException( |     #     raise HTTPException( | ||||||
|     #         status_code=404, |     #         status_code=404, | ||||||
|     #         detail=SnackResponse.error("Unable to Create Mealplan See Log"), |     #         detail=SnackResponse.error("Unable to Create Mealplan See Log"), | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ ENV = CWD.joinpath(".env") | |||||||
| dotenv.load_dotenv(ENV) | dotenv.load_dotenv(ENV) | ||||||
|  |  | ||||||
| # General | # General | ||||||
|  | PRODUCTION = os.environ.get("ENV") | ||||||
| PORT = int(os.getenv("mealie_port", 9000)) | PORT = int(os.getenv("mealie_port", 9000)) | ||||||
|  |  | ||||||
| # Mongo Database | # Mongo Database | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import json | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
| from services.settings_services import Colors, SiteTheme | from services.settings_services import Colors, SiteTheme | ||||||
| @@ -37,5 +38,43 @@ def generate_default_theme(): | |||||||
|         default_theme.save_to_db() |         default_theme.save_to_db() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | """Script to export the ReDoc documentation page into a standalone HTML file.""" | ||||||
|  |  | ||||||
|  | HTML_TEMPLATE = """<!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  |     <meta http-equiv="content-type" content="text/html; charset=UTF-8"> | ||||||
|  |     <title>My Project - ReDoc</title> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  |     <link rel="shortcut icon" href="https://fastapi.tiangolo.com/img/favicon.png"> | ||||||
|  |     <style> | ||||||
|  |         body { | ||||||
|  |             margin: 0; | ||||||
|  |             padding: 0; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  |     <style data-styled="" data-styled-version="4.4.1"></style> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |     <div id="redoc-container"></div> | ||||||
|  |     <script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script> | ||||||
|  |     <script> | ||||||
|  |         var spec = %s; | ||||||
|  |         Redoc.init(spec, {}, document.getElementById("redoc-container")); | ||||||
|  |     </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | CWD = Path(__file__).parent | ||||||
|  | out_path = CWD.joinpath("temp", "api.html") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def generate_api_docs(app): | ||||||
|  |     with open(out_path, "w") as fd: | ||||||
|  |         print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd) | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     pass |     pass | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								mealie/temp/api.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								mealie/temp/api.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1 +0,0 @@ | |||||||
| import datetime |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| // Test Notify |  | ||||||
		Reference in New Issue
	
	Block a user