diff options
author | schencej <schencej@clarkson.edu> | 2019-12-09 17:56:04 -0500 |
---|---|---|
committer | schencej <schencej@clarkson.edu> | 2019-12-09 17:56:04 -0500 |
commit | db8a87f4903ad224238f938d1b36594001e7333f (patch) | |
tree | 58825d7f1a197951126a992441c10ea72604ba38 | |
parent | 2b43753e546b85ad3aea411df9636b23153fb9d8 (diff) | |
parent | c37cbf8fbdb48ce8fc14a76ded2ff5401d7bae4d (diff) |
Merge branch 'master' of https://github.com/tuckerevans/recipe_buddy into elim
-rw-r--r-- | backend/readme.adoc | 94 | ||||
-rw-r--r-- | recipeBuddy/angular.json | 5 | ||||
-rw-r--r-- | recipeBuddy/src/app/DataModels/recipe.ts | 14 | ||||
-rw-r--r-- | recipeBuddy/src/app/add-recipe/add-recipe.component.css | 43 | ||||
-rw-r--r-- | recipeBuddy/src/app/add-recipe/add-recipe.component.html | 109 | ||||
-rw-r--r-- | recipeBuddy/src/app/add-recipe/add-recipe.component.spec.ts | 25 | ||||
-rw-r--r-- | recipeBuddy/src/app/add-recipe/add-recipe.component.ts | 126 | ||||
-rw-r--r-- | recipeBuddy/src/app/app-routing.module.ts | 4 | ||||
-rw-r--r-- | recipeBuddy/src/app/app.module.ts | 18 | ||||
-rw-r--r-- | recipeBuddy/src/index.html | 2 | ||||
-rw-r--r-- | recipeBuddy/src/styles.css | 3 |
11 files changed, 391 insertions, 52 deletions
diff --git a/backend/readme.adoc b/backend/readme.adoc index 0dec0b2..b10ea07 100644 --- a/backend/readme.adoc +++ b/backend/readme.adoc @@ -1,7 +1,7 @@ Backend API =========== Tucker Evans -v1.0, November 22, 2019 +v1.1, December 9, 2019 This REST API allows you to access recipe information in our database with simple HTTP requests. There is currently no authentication/authorization of @@ -16,41 +16,41 @@ The current implementation expects (and returns) recipes in the form: [source,json] ---- { - "Id": 0, - "Title": "Recipe Title", - "Desc": "Recipe Description", - "Photos": [ + "id": 0, + "name": "Recipe name", + "description": "Recipe Description", + "photos": [ "photo_url_1", "photo_url_2" ], - "Serving_size": 0, - "Cook_time": 0, - "Rating": 0, - "Num_cooked": 0, - "Keywords": [ + "servingSize": 0, + "cookTime": 0, + "rating": 0, + "timesCooked": 0, + "tags": [ "keyword 1", "keyword 2", "keyword 3" ], - "Ingredients": [ + "ingredients": [ { - "Name": "Ingredient 1 Name", - "Amount": 1.0, - "Unit": "Ingredient Units" + "name": "Ingredient 1 Name", + "amount": 1.0, + "unit": "Ingredient Units" + "type_": "" }, ], - "Steps": [ + "steps": [ { - "Num": 0, - "Desc": "Step Instructions/Description", - "Time": 0 + "instructions": "Step Instructions/Description", + "timer": 0 } ] } ---- [NOTE] -`"Id"` is not required for a POST request, and will be ignored. +`"id"` is not required for a POST request, and will be ignored. [IMPORTANT] Keywords and Photo URLs are currently stored as pipe separated values, the @@ -110,23 +110,23 @@ recipe object in JSON form: ---- $ curl -X POST api.recipebuddy.xyz:8888/recipes -d ' { - "Title":"Test Recipe 2", - "Desc":"This is a descripiton for the test recipe", - "Photos":["photo_url_1","photo_url_2"], - "Serving_size":0, - "Cook_time":60, - "Rating":5, - "Keywords":["keyword_1", "keyword_2","keyword_3"], - "Ingredients":[ - {"Name":"INGR 1","Amount":2.5,"Unit":"cups"}, - {"Name":"INGR 2","Amount":1,"Unit":"oz"} + "name":"Test Recipe 2", + "description":"This is a descripiton for the test recipe", + "photos":["photo_url_1","photo_url_2"], + "servingSize":0, + "cookTime":60, + "rating":5, + "tags":["keyword_1", "keyword_2","keyword_3"], + "ingredients":[ + {"name":"INGR 1","amount":2.5,"unit":"cups"}, + {"name":"INGR 2","amount":1,"unit":"oz"} ], - "Steps":[ - {"Num":1,"Desc":"Step 1: Do this first","Time":10} + "steps":[ + {"instructions":"Step 1: Do this first","timer":10} ] }' -{"Status":{"Code":201,"Msg":"Recipe added successfully"},"Data":{"Id":2,"Title":"Test Recipe 2","Desc":"This is a descripiton for the test recipe","Photos":["photo_url_1","photo_url_2"],"Serving_size":0,"Cook_time":60,"Rating":5,"Num_cooked":0,"Keywords":["keyword_1","keyword_2","keyword_3"],"Ingredients":[{"Name":"INGR 1","Amount":2.5,"Unit":"cups"},{"Name":"INGR 2","Amount":1,"Unit":"oz"}],"Steps":[{"Num":1,"Desc":"Step 1: Do this first","Time":10}]}} +{"Status":{"Code":201,"Msg":"Recipe added successfully"},"Data":{"id":2,"name":"Test Recipe 2","description":"This is a descripiton for the test recipe","photos":["photo_url_1","photo_url_2"],"servingSize":0,"cookTime":60,"rating":5,"timesCooked":0,"tags":["keyword_1","keyword_2","keyword_3"],"ingredients":[{"name":"INGR 1","amount":2.5,"unit":"cups"},{"name":"INGR 2","amount":1,"unit":"oz"}],"steps":[{"instructions":"Step 1: Do this first","timer":10}]}} ---- Read @@ -138,7 +138,7 @@ http://api.recipebuddy.xyz:8888/recipes/0[`/recipes/{id}`], the HTTP body is ign ---- $ curl -X GET api.recipebuddy.xyz:8888/recipes/1 -{"Status":{"Code":200,"Msg":"Successful"},"Data":{"Id":1,"Title":"Test Recipe","Desc":"This is a descripiton for the test recipe","Photos":["photo_url_1","photo_url_2",""],"Serving_size":0,"Cook_time":60,"Rating":5,"Num_cooked":0,"Keywords":["keyword_1","keyword_2","keyword_3",""],"Ingredients":[{"Name":"INGR 1","Amount":2.5,"Unit":"cups"},{"Name":"INGR 2","Amount":1,"Unit":"oz"}],"Steps":[{"Num":1,"Desc":"Step 1: Do this first","Time":10}]}} +{"Status":{"Code":200,"Msg":"Successful"},"Data":{"id":1,"name":"Test Recipe","description":"This is a descripiton for the test recipe","photos":["photo_url_1","photo_url_2",""],"servingSize":0,"cookTime":60,"rating":5,"timesCooked":0,"tags":["keyword_1","keyword_2","keyword_3",""],"ingredients":[{"name":"INGR 1","amount":2.5,"unit":"cups"},{"name":"INGR 2","amount":1,"unit":"oz"}],"steps":[{"instructions":"Step 1: Do this first","timer":10}]}} ---- To access a list of all recipe ids in the database send a `GET` request to @@ -158,24 +158,24 @@ complete recipe in JSON form. ---- $ curl -X PUT localhost:8888/recipes/1 -d ' { - "Id": 1, - "Title":"Test Recipe 1", - "Desc":"This is a descripiton for the test recipe", - "Photos":[ "photo_url_1", "photo_url_2" ], - "Serving_size":0, - "Cook_time":60, - "Rating":5, - "Keywords":[ "keyword_1", "keyword_2", "keyword_3" ], - "Ingredients":[ - { "Name":"INGR 1", "Amount":2.5, "Unit":"cups" }, - { "Name":"INGR 2", "Amount":1, "Unit":"oz" } + "id": 1, + "name":"Test Recipe 1", + "description":"This is a descripiton for the test recipe", + "photos":[ "photo_url_1", "photo_url_2" ], + "servingSize":0, + "cookTime":60, + "rating":5, + "tags":[ "keyword_1", "keyword_2", "keyword_3" ], + "ingredients":[ + { "name":"INGR 1", "amount":2.5, "unit":"cups" }, + { "name":"INGR 2", "amount":1, "unit":"oz" } ], - "Steps":[ - { "Num":0, "Desc":"Step 1: Do this first", "Time":10 } + "steps":[ + { "instructions":"Step 1: Do this first", "timer":10 } ] }' -{"Status":{"Code":201,"Msg":"Recipe added successfully"},"Data":{"Id":1,"Title":"Test Recipe 1","Desc":"This is a descripiton for the test recipe","Photos":["photo_url_1","photo_url_2"],"Serving_size":0,"Cook_time":60,"Rating":5,"Num_cooked":0,"Keywords":["keyword_1","keyword_2","keyword_3"],"Ingredients":[{"Name":"INGR 1","Amount":2.5,"Unit":"cups"},{"Name":"INGR 2","Amount":1,"Unit":"oz"}],"Steps":[{"Num":0,"Desc":"Step 1: Do this first","Time":10}]}} +{"Status":{"Code":201,"Msg":"Recipe added successfully"},"Data":{"id":1,"name":"Test Recipe 1","description":"This is a descripiton for the test recipe","photos":["photo_url_1","photo_url_2"],"servingSize":0,"cookTime":60,"rating":5,"timesCooked":0,"tags":["keyword_1","keyword_2","keyword_3"],"ingredients":[{"name":"INGR 1","amount":2.5,"unit":"cups"},{"name":"INGR 2","amount":1,"unit":"oz"}],"steps":[{"instructions":"Step 1: Do this first","timer":10}]}} ---- [WARNING] diff --git a/recipeBuddy/angular.json b/recipeBuddy/angular.json index f316a85..982ce12 100644 --- a/recipeBuddy/angular.json +++ b/recipeBuddy/angular.json @@ -24,6 +24,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] @@ -89,6 +90,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] @@ -120,6 +122,7 @@ } } } - }}, + } + }, "defaultProject": "recipeBuddy" }
\ No newline at end of file diff --git a/recipeBuddy/src/app/DataModels/recipe.ts b/recipeBuddy/src/app/DataModels/recipe.ts index 9d833be..18c4716 100644 --- a/recipeBuddy/src/app/DataModels/recipe.ts +++ b/recipeBuddy/src/app/DataModels/recipe.ts @@ -14,7 +14,17 @@ export class Recipe { public tags: string[]; public photos: string[]; - public constructor(id: number, name: string, description: string, ingredients: Ingredient[], steps: Step[], servingSize: number, cookTime: number, rating: number, tags: string[]) { + public constructor(id: number, + name: string, + description: string, + ingredients: Ingredient[], + steps: Step[], + servingSize: number, + cookTime: number, + timesCooked: number, + rating: number, + tags: string[], + photos: string[]) { this.id = id; this.name = name; this.description = description; @@ -24,6 +34,8 @@ export class Recipe { this.cookTime = cookTime; this.rating = rating; this.tags = tags; + this.photos = photos; + this.timesCooked = timesCooked; } public getId(): number { diff --git a/recipeBuddy/src/app/add-recipe/add-recipe.component.css b/recipeBuddy/src/app/add-recipe/add-recipe.component.css new file mode 100644 index 0000000..9d74c53 --- /dev/null +++ b/recipeBuddy/src/app/add-recipe/add-recipe.component.css @@ -0,0 +1,43 @@ +.form { + min-width: 150px; + max-width: 750px; + width: 95%; + margin-left: auto; + margin-right: auto; + padding-left: 5px; + padding-right: 5px; +} + +.full-width { + width: 100%; +} + +.half-width { + width: 50%; + padding: 1%; + margin-left:auto; + margin-right:auto; +} + +.quarter-width { + width: 25%; + padding: 1%; + margin-left:auto; + margin-right:auto; +} + +.third-width { + width: 33%; + padding: 1%; + margin-left:auto; + margin-right:auto; +} + +td { + padding-right: 8px; +} + +.center { + margin-left:auto; + margin-right:auto; +} diff --git a/recipeBuddy/src/app/add-recipe/add-recipe.component.html b/recipeBuddy/src/app/add-recipe/add-recipe.component.html new file mode 100644 index 0000000..76095f9 --- /dev/null +++ b/recipeBuddy/src/app/add-recipe/add-recipe.component.html @@ -0,0 +1,109 @@ +<form [formGroup]="recipeForm" class="form" (ngSubmit)="onSubmit()"> +<h2>Add Recipe</h2> + <mat-form-field class="full-width"> + <input matInput placeholder="Name" type="text" + formControlName="recipeName" required> + <mat-hint> + Name is required + </mat-hint> + <mat-error> + Name is required + </mat-error> + </mat-form-field> + <mat-form-field class="full-width"> + <textarea matInput placeholder="Description" + formControlName="desc"> TEST </textarea> + </mat-form-field> + <mat-form-field class="full-width"> + <input matInput placeholder="Servings" type=text + formControlName="servingSize" pattern="^[0-9]*(\.[0-9]*)?$"> + <mat-error> + Servings must be a number. + </mat-error> + </mat-form-field> + <mat-form-field class="full-width"> + + <input matInput placeholder="Cooking Time" type="text" formControlName="cookTime" + pattern="^[0-9]*$"> + <span matSuffix>Minutes</span> + <mat-error> + Must be in the form hh:mm + </mat-error> + </mat-form-field> + <mat-form-field class="full-width" floatLabel="options.value.floatLabel"> + <mat-label>Keywords/Tags</mat-label> + <input matInput placeholder="Separate with a comma" type="text" formControlName="tags"> + </mat-form-field> + <mat-form-field class="full-width" floatLabel="options.value.floatLabel"> + <mat-label>Photos (URLS)</mat-label> + <input matInput placeholder="Separate with a comma" type="text" formControlName="photos"> + </mat-form-field> + + <div formArrayName="ingredients"> + <h3>Ingredients</h3> + <div *ngFor="let ingr of ingredients.controls; let i=index"> + <div [formGroupName]="i"> + <h4>Ingredient {{ i + 1 }}</h4> + <div class="full-width"> + <mat-form-field class="quarter-width"> + <input matInput placeholder="Name" type="text" + formControlName="ingrName"> + </mat-form-field> + <mat-form-field class="quarter-width"> + <input matInput placeholder="Amount" + type="text" + formControlName="amount" pattern="^[0-9]*(\.[0-9]*)?$"> + <mat-error> + Amount must be a number. + </mat-error> + </mat-form-field> + <mat-form-field class="quarter-width"> + <input matInput placeholder="Units" type="text" + formControlName="units"> + </mat-form-field> + <button matSuffix mat-mini-fab (click)="rmIngredient(i)" + type="button" style="margin-left: 10px"> + <mat-icon>remove</mat-icon> + </button> + </div> + </div> + </div> + <div style="text-align: center"> + <button mat-mini-fab (click)="addIngredient()" + type="button"> + <mat-icon>add</mat-icon> + </button> + </div> + </div> + + <div formArrayName="steps"> + <h3>Steps</h3> + <div *ngFor="let address of steps.controls; let i=index"> + <div [formGroupName]="i"> + <h4>Step {{ i + 1 }}</h4> + <div class="ful-width"> + <mat-form-field class="half-width"> + <textarea matInput placeholder="Instructions" type="text" formControlName="instruct"> + </textarea> + </mat-form-field> + <mat-form-field class="quarter-width"> + <input matInput placeholder="Timer" type="text" + formControlName="timer" pattern="^[0-9]*$"> + <span matSuffix>Minutes</span> + </mat-form-field> + <button matSuffix mat-mini-fab (click)="rmStep(i)" + type="button" style="margin-left: 10px"> + <mat-icon>remove</mat-icon> + </button> + </div> + </div> + </div> + <div style="text-align: center"> + <button mat-mini-fab (click)="addStep()" matSuffix type="button"> + <mat-icon>add</mat-icon> + </button> + </div> + </div> + <button mat-flat-button color="primary" type="submit" + [disabled]="!recipeForm.valid">Submit</button> +</form> diff --git a/recipeBuddy/src/app/add-recipe/add-recipe.component.spec.ts b/recipeBuddy/src/app/add-recipe/add-recipe.component.spec.ts new file mode 100644 index 0000000..86b2da6 --- /dev/null +++ b/recipeBuddy/src/app/add-recipe/add-recipe.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddRecipeComponent } from './add-recipe.component'; + +describe('AddRecipeComponent', () => { + let component: AddRecipeComponent; + let fixture: ComponentFixture<AddRecipeComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AddRecipeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddRecipeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/recipeBuddy/src/app/add-recipe/add-recipe.component.ts b/recipeBuddy/src/app/add-recipe/add-recipe.component.ts new file mode 100644 index 0000000..407997c --- /dev/null +++ b/recipeBuddy/src/app/add-recipe/add-recipe.component.ts @@ -0,0 +1,126 @@ +import { Component, OnInit } from '@angular/core'; + +import { FormControl } from '@angular/forms'; + +import { FormBuilder } from '@angular/forms'; +import { FormArray } from '@angular/forms'; + +import { Validators } from '@angular/forms'; + +import { Recipe } from '../DataModels/recipe'; +import { Ingredient } from '../DataModels/ingredient' +import { Step } from '../DataModels/step'; +import { BackendService } from '../REST_service/backend.service'; + +@Component({ + selector: 'app-add-recipe', + templateUrl: './add-recipe.component.html', + styleUrls: ['./add-recipe.component.css'] +}) + +export class AddRecipeComponent { + + recipeForm = this.fb.group({ + recipeName: ['', Validators.required], + desc: [''], + ingredients: this.fb.array([ + this.fb.group({ + ingrName: [''], + amount: ['', Validators.pattern('^[0-9]*(\.[0-9]*)?$')], + units: [''] + }) + ]), + steps: this.fb.array([ + this.fb.group({ + instruct: [''], + timer: [''] + }) + ]), + servingSize: ['', Validators.pattern('^[0-9]*(\.[0-9]*)?$')], + cookTime: ['', Validators.pattern('^[0-9]*$')], + tags: [''], + photos: [''] + }); + + constructor(private fb: FormBuilder, + private restService: BackendService + ) { } + + ngOnInit() { + } + + get ingredients() { + return this.recipeForm.get('ingredients') as FormArray; + } + + addIngredient() { + this.ingredients.push( + this.fb.group({ + ingrName: [''], + amount: ['', Validators.pattern('^[0-9]*(\.[0-9]*)?$')], + units: [''] + }) + ); + } + + rmIngredient(i) { + this.ingredients.removeAt(i); + } + + get steps() { + return this.recipeForm.get('steps') as FormArray; + } + + addStep() { + this.steps.push( + this.fb.group({ + instruct: [''], + timer: ['', Validators.pattern('^[0-9]*$')] + }) + ); + } + + rmStep(i) { + this.steps.removeAt(i); + } + + onSubmit() { + var formData = this.recipeForm.value; + + var ingredients = [] + var i; + for (i = 0; i < formData.ingredients.length; i++) { + var tmp_amount = parseFloat(formData.ingredients[0].amount) + ingredients.push(new Ingredient(formData.ingredients[0].ingrName, + (isNaN(tmp_amount) ? 0 : tmp_amount), + formData.ingredients[0].unit, + "" + )); + } + + var steps = [] + for (i = 0; i < formData.ingredients.length; i++) { + var tmp_timer = parseInt(formData.steps[0].timer) + steps.push(new Step(formData.steps[0].instruct, + (isNaN(tmp_timer) ? 0 : tmp_timer) + )); + } + + var servingsTmp = parseFloat(formData.servingSize) + var cookTimeTmp = parseInt(formData.cookTime) + + var recipe = new Recipe (0, //id + formData.recipeName, //name + formData.desc, //description + ingredients, //ingredients + steps, //steps + (isNaN(servingsTmp) ? 0 :servingsTmp), //servingSize + (isNaN(cookTimeTmp) ? 0 :cookTimeTmp), //cookTime + 0, //timesCooked + 0, //rating + formData.tags.split(','), //tags + formData.photos.split(',') //photos + ); + this.restService.createRecipe(recipe).subscribe() + } +} diff --git a/recipeBuddy/src/app/app-routing.module.ts b/recipeBuddy/src/app/app-routing.module.ts index 3e4d67a..049bf74 100644 --- a/recipeBuddy/src/app/app-routing.module.ts +++ b/recipeBuddy/src/app/app-routing.module.ts @@ -3,13 +3,13 @@ import { RouterModule, Routes } from '@angular/router'; import { CookPageComponent } from './cook-page/cook-page.component'; import { PreCookPopUpComponent } from './pre-cook-pop-up/pre-cook-pop-up.component'; +import { AddRecipeComponent } from './add-recipe/add-recipe.component'; const routes: Routes = [ { path: '', redirectTo: '/cook', pathMatch: 'full' }, {path: 'preCook' , component: PreCookPopUpComponent }, + { path: 'add', component: AddRecipeComponent }, { path: 'cook', component: CookPageComponent } -]; - @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] diff --git a/recipeBuddy/src/app/app.module.ts b/recipeBuddy/src/app/app.module.ts index 9b7a049..a2db49c 100644 --- a/recipeBuddy/src/app/app.module.ts +++ b/recipeBuddy/src/app/app.module.ts @@ -12,18 +12,34 @@ import { AppRoutingModule } from './app-routing.module'; import { HttpClientModule } from '@angular/common/http'; import { PreCookPopUpComponent } from './pre-cook-pop-up/pre-cook-pop-up.component'; import { RecipePassService } from './recipePass/recipe-pass.service' +import { AddRecipeComponent } from './add-recipe/add-recipe.component'; + +import { ReactiveFormsModule } from '@angular/forms'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; @NgModule({ declarations: [ AppComponent, CookPageComponent, PreCookPopUpComponent, + AddRecipeComponent ], imports: [ BrowserModule, AppRoutingModule, MatCardModule, - HttpClientModule + HttpClientModule, + ReactiveFormsModule, + NoopAnimationsModule, + MatInputModule, + MatFormFieldModule, + MatIconModule, + MatButtonModule ], providers: [RecipePassService], bootstrap: [AppComponent], diff --git a/recipeBuddy/src/index.html b/recipeBuddy/src/index.html index 122efbf..29b1c1b 100644 --- a/recipeBuddy/src/index.html +++ b/recipeBuddy/src/index.html @@ -6,6 +6,8 @@ <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> + <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet"> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> </head> <body> <app-root></app-root> diff --git a/recipeBuddy/src/styles.css b/recipeBuddy/src/styles.css index 90d4ee0..7e7239a 100644 --- a/recipeBuddy/src/styles.css +++ b/recipeBuddy/src/styles.css @@ -1 +1,4 @@ /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } |