From ef99e5c56d1ad953ebb55dd1fbca010a5d19ad3b Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:20:06 +0200 Subject: [PATCH 01/97] =?UTF-8?q?=F0=9F=9A=A7=20Adding=20barcode=20scanner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/capacitor.build.gradle | 3 +- android/app/src/main/AndroidManifest.xml | 8 ++++ android/capacitor.settings.gradle | 3 ++ .../models/mongo/VisitedLinkModel.java | 2 + .../src/app/pages/pantry/pantry.page.html | 36 ++++++++-------- ios/App/App/Info.plist | 2 + ios/App/Podfile | 1 + package-lock.json | 43 ++++++++++++++++++- package.json | 4 +- 9 files changed, 81 insertions(+), 21 deletions(-) diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index e15a0e9c..e93da780 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -9,12 +9,13 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-mlkit-barcode-scanning') implementation project(':capacitor-app') implementation project(':capacitor-haptics') implementation project(':capacitor-http') implementation project(':capacitor-keyboard') implementation project(':capacitor-status-bar') - + implementation "androidx.webkit:webkit:1.4.0" } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4d7ca380..166ce48a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,8 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> + + + + + + + + diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 1be23e75..96c35706 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -2,6 +2,9 @@ include ':capacitor-android' project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') +include ':capacitor-mlkit-barcode-scanning' +project(':capacitor-mlkit-barcode-scanning').projectDir = new File('../node_modules/@capacitor-mlkit/barcode-scanning/android') + include ':capacitor-app' project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java index 35d17eaa..b80dd542 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java @@ -2,6 +2,7 @@ import java.time.LocalDate; +import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.Getter; @@ -11,6 +12,7 @@ @Setter @Getter public class VisitedLinkModel { + @Id private String link; private LocalDate lastVisited; diff --git a/frontend/src/app/pages/pantry/pantry.page.html b/frontend/src/app/pages/pantry/pantry.page.html index 1d55ef00..3079b88f 100644 --- a/frontend/src/app/pages/pantry/pantry.page.html +++ b/frontend/src/app/pages/pantry/pantry.page.html @@ -95,17 +95,17 @@ horizontal="end" *ngSwitchCase="'pantry'" > - - + + - + + + + - - + + - + UIViewControllerBasedStatusBarAppearance + NSCameraUsageDescription + $(PRODUCT_NAME) uses the camera to scan barcodes. diff --git a/ios/App/Podfile b/ios/App/Podfile index 97f0aea8..eed38fa3 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -11,6 +11,7 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' + pod 'CapacitorMlkitBarcodeScanning', :path => '../../node_modules/@capacitor-mlkit/barcode-scanning' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' pod 'CapacitorHttp', :path => '../../node_modules/@capacitor/http' diff --git a/package-lock.json b/package-lock.json index eaaeadfd..b981493c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "@angular/platform-browser-dynamic": "^15.0.0", "@angular/router": "^15.0.0", "@awesome-cordova-plugins/http": "^6.3.0", - "@capacitor/android": "5.0.5", + "@capacitor-mlkit/barcode-scanning": "^5.1.0", + "@capacitor/android": "^5.0.5", "@capacitor/app": "^5.0.3", "@capacitor/core": "5.0.5", "@capacitor/haptics": "^5.0.4", @@ -27,6 +28,7 @@ "@types/chart.js": "^2.9.37", "chart.js": "^4.3.0", "cordova-plugin-advanced-http": "^3.3.1", + "cordova-plugin-file": "^8.0.0", "dotenv": "^16.3.1", "ionicons": "^7.0.0", "neo4j-driver": "^5.10.0", @@ -2573,6 +2575,24 @@ "node": ">=6.9.0" } }, + "node_modules/@capacitor-mlkit/barcode-scanning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@capacitor-mlkit/barcode-scanning/-/barcode-scanning-5.1.0.tgz", + "integrity": "sha512-4ddwHQa8uUL9+px6Z/khXV/46V00l218pagsoMOGbo8dSYbpu8RCgsvnacG4igJy3DAQsPoRoLbSbgSqNif4QA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/capawesome-team/" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/capawesome" + } + ], + "peerDependencies": { + "@capacitor/core": "^5.0.0" + } + }, "node_modules/@capacitor/android": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.0.5.tgz", @@ -6102,6 +6122,27 @@ } ] }, + "node_modules/cordova-plugin-file": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-8.0.0.tgz", + "integrity": "sha512-pgxCJtDjDKzyeqvrn0KnDubf9b1VLv+OyWTXjUR7T52o7oGDUkR3ubT89i/1ugHtRU6mY7XIGHD4drUByDQClw==", + "engines": { + "cordovaDependencies": { + "5.0.0": { + "cordova-android": ">=6.3.0" + }, + "7.0.0": { + "cordova-android": ">=10.0.0" + }, + "8.0.0": { + "cordova-android": ">=12.0.0" + }, + "9.0.0": { + "cordova": ">100" + } + } + } + }, "node_modules/core-js-compat": { "version": "3.30.2", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", diff --git a/package.json b/package.json index a36fd174..936d32f6 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "@angular/platform-browser-dynamic": "^15.0.0", "@angular/router": "^15.0.0", "@awesome-cordova-plugins/http": "^6.3.0", - "@capacitor/android": "5.0.5", + "@capacitor-mlkit/barcode-scanning": "^5.1.0", + "@capacitor/android": "^5.0.5", "@capacitor/app": "^5.0.3", "@capacitor/core": "5.0.5", "@capacitor/haptics": "^5.0.4", @@ -34,6 +35,7 @@ "@types/chart.js": "^2.9.37", "chart.js": "^4.3.0", "cordova-plugin-advanced-http": "^3.3.1", + "cordova-plugin-file": "^8.0.0", "dotenv": "^16.3.1", "ionicons": "^7.0.0", "neo4j-driver": "^5.10.0", From 6c81c19a9f33f6db3298b2a532f74567945e968b Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:55:37 +0200 Subject: [PATCH 02/97] =?UTF-8?q?=E2=9C=A8=20Frontend=20barcode=20function?= =?UTF-8?q?ality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/pages/pantry/pantry.page.html | 16 ++++++- .../src/app/pages/pantry/pantry.page.scss | 8 ++++ frontend/src/app/pages/pantry/pantry.page.ts | 43 +++++++++++++++++++ .../barcode-api/barcode-api.service.spec.ts | 16 +++++++ .../barcode-api/barcode-api.service.ts | 23 ++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/services/barcode-api/barcode-api.service.spec.ts create mode 100644 frontend/src/app/services/barcode-api/barcode-api.service.ts diff --git a/frontend/src/app/pages/pantry/pantry.page.html b/frontend/src/app/pages/pantry/pantry.page.html index 3079b88f..a7d5cdde 100644 --- a/frontend/src/app/pages/pantry/pantry.page.html +++ b/frontend/src/app/pages/pantry/pantry.page.html @@ -102,7 +102,13 @@ - + + + @@ -185,7 +191,13 @@ - + + + diff --git a/frontend/src/app/pages/pantry/pantry.page.scss b/frontend/src/app/pages/pantry/pantry.page.scss index d6856927..58f0506b 100644 --- a/frontend/src/app/pages/pantry/pantry.page.scss +++ b/frontend/src/app/pages/pantry/pantry.page.scss @@ -72,3 +72,11 @@ ion-segment-button.md { --color-checked: var(--ion-color-primary); --indicator-height: 4px; } + +ion-fab-button.fab-button-disabled { + --background: #7fb688 !important; +} + +ion-fab-button.fab-button-in-list { + --background: #acf7b7; +} diff --git a/frontend/src/app/pages/pantry/pantry.page.ts b/frontend/src/app/pages/pantry/pantry.page.ts index a921fe2b..663b4bd2 100644 --- a/frontend/src/app/pages/pantry/pantry.page.ts +++ b/frontend/src/app/pages/pantry/pantry.page.ts @@ -12,6 +12,11 @@ import { FormsModule } from '@angular/forms'; import { FoodListItemComponent } from '../../components/food-list-item/food-list-item.component'; import { FoodItemI } from '../../models/interfaces'; import { OverlayEventDetail } from '@ionic/core/components'; +import { + BarcodeScanner, + Barcode, + ScanResult, +} from '@capacitor-mlkit/barcode-scanning'; import { AuthenticationService, ErrorHandlerService, @@ -32,6 +37,7 @@ export class PantryPage implements OnInit, ViewWillEnter { foodListItem!: QueryList; @ViewChild(IonModal) modal!: IonModal; + isBarcodeSupported: boolean = false; segment: 'pantry' | 'shopping' | null = 'pantry'; isLoading: boolean = false; pantryItems: FoodItemI[] = []; @@ -54,6 +60,10 @@ export class PantryPage implements OnInit, ViewWillEnter { ) {} async ngOnInit() { + BarcodeScanner.isSupported().then((result) => { + this.isBarcodeSupported = result.supported; + }); + this.fetchItems(); } @@ -441,4 +451,37 @@ export class PantryPage implements OnInit, ViewWillEnter { this.shoppingItems.sort(sortFunction); } } + + async scan(): Promise { + const granted = await this.requestPermissions(); + if (!granted) { + this.errorHandlerService.presentErrorToast( + 'Please grant camera permissions to use this feature', + 'Camera permissions not granted' + ); + return; + } + + const result = await BarcodeScanner.scan(); + + if ( + result.barcodes.length === 0 || + result.barcodes[0].displayValue === '' || + result.barcodes[0].displayValue === null || + result.barcodes[0].displayValue === undefined + ) { + return; + } + + this.sendBarcode(result); + } + + async requestPermissions(): Promise { + const { camera } = await BarcodeScanner.requestPermissions(); + return camera === 'granted' || camera === 'limited'; + } + + async sendBarcode(result: ScanResult): Promise { + let code = result.barcodes[0].displayValue; + } } diff --git a/frontend/src/app/services/barcode-api/barcode-api.service.spec.ts b/frontend/src/app/services/barcode-api/barcode-api.service.spec.ts new file mode 100644 index 00000000..e07f0099 --- /dev/null +++ b/frontend/src/app/services/barcode-api/barcode-api.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { BarcodeApiService } from './barcode-api.service'; + +describe('BarcodeApiService', () => { + let service: BarcodeApiService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(BarcodeApiService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/services/barcode-api/barcode-api.service.ts b/frontend/src/app/services/barcode-api/barcode-api.service.ts new file mode 100644 index 00000000..db9685d9 --- /dev/null +++ b/frontend/src/app/services/barcode-api/barcode-api.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { FoodItemI } from '../../models/interfaces'; + +@Injectable({ + providedIn: 'root', +}) +export class BarcodeApiService { + url: String = 'http://localhost:8080'; + + constructor(private http: HttpClient) {} + + findProduct(barcode: String): Observable> { + return this.http.post( + this.url + '/findProduct', + { + barcode: barcode, + }, + { observe: 'response' } + ); + } +} From a409bcc8d7146cdbc8f29fb3999c58ab5a38072d Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Thu, 7 Sep 2023 20:29:55 +0200 Subject: [PATCH 03/97] =?UTF-8?q?=F0=9F=90=9B=20Bug=20fix=20and=20Barcode?= =?UTF-8?q?=20frontend=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/ProductController.java | 37 ++++++++ .../mealmaestro/models/mongo/Barcode.java | 17 ++++ .../mealmaestro/models/mongo/FoodModelM.java | 6 +- .../models/mongo/VisitedLinkModel.java | 3 +- .../repositories/mongo/FoodMRepository.java | 2 +- .../mongo/VisitedLinkRepository.java | 3 +- .../mealmaestro/services/BarcodeService.java | 34 +++++++ .../services/MealDatabaseService.java | 2 + .../services/MealManagementService.java | 1 + .../app/pages/acc-profile/acc-profile.page.ts | 4 +- frontend/src/app/pages/home/home.page.ts | 2 +- frontend/src/app/pages/pantry/pantry.page.ts | 92 +++++++++++++------ .../src/app/pages/profile/profile.page.ts | 33 +------ .../app/pages/recipe-book/recipe-book.page.ts | 8 +- .../src/app/services/login/login.service.ts | 13 +++ frontend/src/app/services/services.ts | 1 + 16 files changed, 185 insertions(+), 73 deletions(-) create mode 100644 backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java new file mode 100644 index 00000000..92a0e478 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java @@ -0,0 +1,37 @@ +package fellowship.mealmaestro.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import fellowship.mealmaestro.models.mongo.Barcode; +import fellowship.mealmaestro.models.mongo.FoodModelM; +import fellowship.mealmaestro.services.BarcodeService; + +@RestController +public class ProductController { + + @Autowired + private BarcodeService barcodeService; + + @PostMapping("/findProduct") + public ResponseEntity findProduct(@RequestBody Barcode request, + @RequestHeader("Authorization") String token) { + if (token == null || token.isEmpty()) { + return ResponseEntity.badRequest().build(); + } + return ResponseEntity.ok(barcodeService.findProduct(request.getBarcode())); + } + + @PostMapping("/addProduct") + public ResponseEntity addProduct(@RequestBody FoodModelM product, + @RequestHeader("Authorization") String token) { + if (token == null || token.isEmpty()) { + return ResponseEntity.badRequest().build(); + } + return ResponseEntity.ok(barcodeService.addProduct(product)); + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java new file mode 100644 index 00000000..1bce9832 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java @@ -0,0 +1,17 @@ +package fellowship.mealmaestro.models.mongo; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Barcode { + String barcode; + + public Barcode() { + } + + public Barcode(String barcode) { + this.barcode = barcode; + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java index fcf141f3..8dc7e542 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java @@ -21,7 +21,8 @@ public class FoodModelM { private double price; - public FoodModelM(String barcode, String name, double quantity, String unit, double price) { + public FoodModelM(String barcode, String name, double quantity, String unit, + double price) { this.barcode = barcode; this.name = name; this.quantity = quantity; @@ -34,7 +35,8 @@ public FoodModelM() { @Override public String toString() { - return "FoodModelM [barcode=" + barcode + ", name=" + name + ", price=" + price + ", quantity=" + quantity + return "FoodModelM [barcode=" + barcode + ", name=" + name + ", price=" + + price + ", quantity=" + quantity + ", unit=" + unit + "]"; } } diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java index b80dd542..9a2ea0be 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java @@ -27,6 +27,7 @@ public VisitedLinkModel() { @Override public String toString() { - return "VisitedLinkModel [lastVisited=" + lastVisited + ", link=" + link + "]"; + return "VisitedLinkModel [lastVisited=" + lastVisited + ", link=" + link + + "]"; } } diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java index 8105354d..71ebbd97 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java @@ -5,5 +5,5 @@ import fellowship.mealmaestro.models.mongo.FoodModelM; public interface FoodMRepository extends MongoRepository { - + FoodModelM findByBarcode(String barcode); } diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/VisitedLinkRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/VisitedLinkRepository.java index bb7aeb9f..3a3226a9 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/VisitedLinkRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/VisitedLinkRepository.java @@ -4,6 +4,7 @@ import fellowship.mealmaestro.models.mongo.VisitedLinkModel; -public interface VisitedLinkRepository extends MongoRepository { +public interface VisitedLinkRepository extends + MongoRepository { } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java b/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java new file mode 100644 index 00000000..727ed20b --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java @@ -0,0 +1,34 @@ +package fellowship.mealmaestro.services; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import fellowship.mealmaestro.models.mongo.FoodModelM; +import fellowship.mealmaestro.repositories.mongo.FoodMRepository; + +@Service +public class BarcodeService { + + @Autowired + private FoodMRepository foodMRepository; + + public FoodModelM findProduct(String code) { + System.out.println(code); + Optional product = foodMRepository.findById(code); + if (product.isEmpty()) { + FoodModelM nullProduct = new FoodModelM(); + nullProduct.setName(""); + nullProduct.setQuantity(0); + nullProduct.setUnit("pcs"); + return nullProduct; + } else { + return product.get(); + } + } + + public FoodModelM addProduct(FoodModelM product) { + return foodMRepository.save(product); + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java index 515f6d0f..524784f7 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java @@ -72,6 +72,7 @@ public List saveMeals(List mealsToSave, LocalDate date, St return meals; } + @Transactional public List findUsersMealPlanForDate(LocalDate date, String token) { removeOldMeals(token); @@ -117,6 +118,7 @@ public void removeOldMeals(String token) { userRepository.save(user); } + @Transactional public Optional findMealTypeForUser(String type, String token) { String email = jwtService.extractUserEmail(token); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java index b240700b..4c51817e 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java @@ -32,6 +32,7 @@ public MealModel generateMeal(String mealType, String token) { MealModel defaultMeal = new MealModel("Bread", "1. Toast the bread", "Delicious Bread", "https://images.unsplash.com/photo-1598373182133-52452f7691ef?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80", "Bread", "5 minutes"); + defaultMeal.setType("breakfast"); try { JsonNode mealJson = objectMapper.readTree(openaiApiService.fetchMealResponse(mealType, token)); int i = 0; diff --git a/frontend/src/app/pages/acc-profile/acc-profile.page.ts b/frontend/src/app/pages/acc-profile/acc-profile.page.ts index 964389eb..6deea09a 100644 --- a/frontend/src/app/pages/acc-profile/acc-profile.page.ts +++ b/frontend/src/app/pages/acc-profile/acc-profile.page.ts @@ -52,9 +52,7 @@ export class AccProfilePage implements OnInit, ViewWillEnter { this.getUserInfo(); } - ngOnInit() { - this.getUserInfo(); - } + ngOnInit() {} async getUserInfo() { this.auth.getUser().subscribe({ diff --git a/frontend/src/app/pages/home/home.page.ts b/frontend/src/app/pages/home/home.page.ts index 66dc6a10..6fe1a632 100644 --- a/frontend/src/app/pages/home/home.page.ts +++ b/frontend/src/app/pages/home/home.page.ts @@ -64,7 +64,7 @@ export class HomePage implements OnInit, ViewWillEnter { ); }); - await this.getMeals(); + // await this.getMeals(); } async ionViewWillEnter() { diff --git a/frontend/src/app/pages/pantry/pantry.page.ts b/frontend/src/app/pages/pantry/pantry.page.ts index 663b4bd2..d19f73f2 100644 --- a/frontend/src/app/pages/pantry/pantry.page.ts +++ b/frontend/src/app/pages/pantry/pantry.page.ts @@ -23,6 +23,7 @@ import { LoginService, PantryApiService, ShoppingListApiService, + BarcodeApiService, } from '../../services/services'; @Component({ @@ -37,7 +38,7 @@ export class PantryPage implements OnInit, ViewWillEnter { foodListItem!: QueryList; @ViewChild(IonModal) modal!: IonModal; - isBarcodeSupported: boolean = false; + isBarcodeSupported: boolean = true; segment: 'pantry' | 'shopping' | null = 'pantry'; isLoading: boolean = false; pantryItems: FoodItemI[] = []; @@ -56,15 +57,14 @@ export class PantryPage implements OnInit, ViewWillEnter { private shoppingListService: ShoppingListApiService, private errorHandlerService: ErrorHandlerService, private auth: AuthenticationService, - private loginService: LoginService + private loginService: LoginService, + private barcodeApiService: BarcodeApiService ) {} async ngOnInit() { - BarcodeScanner.isSupported().then((result) => { - this.isBarcodeSupported = result.supported; - }); - - this.fetchItems(); + // BarcodeScanner.isSupported().then((result) => { + // this.isBarcodeSupported = result.supported; + // }); } async ionViewWillEnter() { @@ -453,25 +453,32 @@ export class PantryPage implements OnInit, ViewWillEnter { } async scan(): Promise { - const granted = await this.requestPermissions(); - if (!granted) { - this.errorHandlerService.presentErrorToast( - 'Please grant camera permissions to use this feature', - 'Camera permissions not granted' - ); - return; - } - - const result = await BarcodeScanner.scan(); - - if ( - result.barcodes.length === 0 || - result.barcodes[0].displayValue === '' || - result.barcodes[0].displayValue === null || - result.barcodes[0].displayValue === undefined - ) { - return; - } + // const granted = await this.requestPermissions(); + // if (!granted) { + // this.errorHandlerService.presentErrorToast( + // 'Please grant camera permissions to use this feature', + // 'Camera permissions not granted' + // ); + // return; + // } + + // const result = await BarcodeScanner.scan(); + + // if ( + // result.barcodes.length === 0 || + // result.barcodes[0].displayValue === '' || + // result.barcodes[0].displayValue === null || + // result.barcodes[0].displayValue === undefined + // ) { + // return; + // } + let result = { + barcodes: [ + { + displayValue: '13761238123', // for testing + }, + ], + }; this.sendBarcode(result); } @@ -481,7 +488,38 @@ export class PantryPage implements OnInit, ViewWillEnter { return camera === 'granted' || camera === 'limited'; } - async sendBarcode(result: ScanResult): Promise { + async sendBarcode(result: any): Promise { + // replace any with ScanResult let code = result.barcodes[0].displayValue; + + this.barcodeApiService.findProduct(code).subscribe({ + next: (response) => { + if (response.status === 200) { + if (response.body) { + this.newItem = { + name: response.body.name, + quantity: response.body.quantity, + unit: response.body.unit, + price: response.body.price, + }; + this.modal.present(); + } + } + }, + error: (err) => { + if (err.status === 403) { + this.errorHandlerService.presentErrorToast( + 'Unauthorized access. Please login again.', + err + ); + this.auth.logout(); + } else { + this.errorHandlerService.presentErrorToast( + 'Error finding product', + err + ); + } + }, + }); } } diff --git a/frontend/src/app/pages/profile/profile.page.ts b/frontend/src/app/pages/profile/profile.page.ts index 69a28181..7097eea9 100644 --- a/frontend/src/app/pages/profile/profile.page.ts +++ b/frontend/src/app/pages/profile/profile.page.ts @@ -109,37 +109,10 @@ export class ProfilePage implements OnInit, ViewWillEnter { cookingToggle: boolean = false; BMIToggle: boolean = false; - ngOnInit() { - this.loadUserSettings(); - this.auth.getUser().subscribe({ - next: (response) => { - if (response.status == 200) { - if (response.body && response.body.name) { - this.user.username = response.body.name; - this.user.email = response.body.email; - this.user.password = response.body.password; - } - } - }, - error: (error) => { - if (error.status === 403) { - this.errorHandlerService.presentErrorToast( - 'Unauthorized access. Please login again.', - error - ); - this.auth.logout(); - } else { - this.errorHandlerService.presentErrorToast( - 'Unexpected error while loading user data', - error - ); - } - }, - }); - } + ngOnInit() {} ionViewWillEnter(): void { - if (this.loginService.isSettingsRefreshed()) { + if (!this.loginService.isSettingsRefreshed()) { this.loadUserSettings(); this.auth.getUser().subscribe({ next: (response) => { @@ -166,7 +139,7 @@ export class ProfilePage implements OnInit, ViewWillEnter { } }, }); - this.loginService.setSettingsRefreshed(false); + this.loginService.setSettingsRefreshed(true); } } diff --git a/frontend/src/app/pages/recipe-book/recipe-book.page.ts b/frontend/src/app/pages/recipe-book/recipe-book.page.ts index 2d52679f..596a0d8a 100644 --- a/frontend/src/app/pages/recipe-book/recipe-book.page.ts +++ b/frontend/src/app/pages/recipe-book/recipe-book.page.ts @@ -32,13 +32,7 @@ export class RecipeBookPage implements OnInit { private loginService: LoginService ) {} - ngOnInit() { - this.addService.recipeItem$.subscribe((recipeItem) => { - if (recipeItem) { - this.addRecipe(recipeItem); - } - }); - } + ngOnInit() {} async ionViewWillEnter() { if (!this.loginService.isRecipeBookRefreshed()) { diff --git a/frontend/src/app/services/login/login.service.ts b/frontend/src/app/services/login/login.service.ts index d50e48c6..5c9ed999 100644 --- a/frontend/src/app/services/login/login.service.ts +++ b/frontend/src/app/services/login/login.service.ts @@ -49,4 +49,17 @@ export class LoginService { this.recipeBookRefreshed = false; this.settingsRefreshed = false; } + + toString(): string { + return ( + 'homeRefreshed: ' + + this.homeRefreshed + + ', pantryRefreshed: ' + + this.pantryRefreshed + + ', recipeBookRefreshed: ' + + this.recipeBookRefreshed + + ', settingsRefreshed: ' + + this.settingsRefreshed + ); + } } diff --git a/frontend/src/app/services/services.ts b/frontend/src/app/services/services.ts index 284eb170..cc9cf886 100644 --- a/frontend/src/app/services/services.ts +++ b/frontend/src/app/services/services.ts @@ -6,3 +6,4 @@ export { RecipeBookApiService } from './recipe-book/recipe-book-api.service'; export { SettingsApiService } from './settings-api/settings-api.service'; export { MealGenerationService } from './meal-generation/meal-generation.service'; export { LoginService } from './login/login.service'; +export { BarcodeApiService } from './barcode-api/barcode-api.service'; From 3a880272d877f49437aa22b853c9e85853570b29 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:21:30 +0200 Subject: [PATCH 04/97] =?UTF-8?q?=E2=9C=A8=20Added=20Dynamic=20collections?= =?UTF-8?q?=20for=20products?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/ProductController.java | 6 ++-- .../mealmaestro/models/mongo/Barcode.java | 17 ----------- .../mealmaestro/models/mongo/FoodModelM.java | 2 ++ .../models/mongo/findBarcodeRequest.java | 19 +++++++++++++ .../mongo/DynamicFoodMRepository.java | 12 ++++++++ .../mongo/DynamicFoodMRepositoryImpl.java | 28 +++++++++++++++++++ .../repositories/mongo/FoodMRepository.java | 2 +- .../mealmaestro/services/BarcodeService.java | 10 ++++--- frontend/src/app/pages/pantry/pantry.page.ts | 22 ++++++++++----- 9 files changed, 86 insertions(+), 32 deletions(-) delete mode 100644 backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/models/mongo/findBarcodeRequest.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepository.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java index 92a0e478..e590aadf 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; -import fellowship.mealmaestro.models.mongo.Barcode; +import fellowship.mealmaestro.models.mongo.findBarcodeRequest; import fellowship.mealmaestro.models.mongo.FoodModelM; import fellowship.mealmaestro.services.BarcodeService; @@ -18,12 +18,12 @@ public class ProductController { private BarcodeService barcodeService; @PostMapping("/findProduct") - public ResponseEntity findProduct(@RequestBody Barcode request, + public ResponseEntity findProduct(@RequestBody findBarcodeRequest request, @RequestHeader("Authorization") String token) { if (token == null || token.isEmpty()) { return ResponseEntity.badRequest().build(); } - return ResponseEntity.ok(barcodeService.findProduct(request.getBarcode())); + return ResponseEntity.ok(barcodeService.findProduct(request)); } @PostMapping("/addProduct") diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java deleted file mode 100644 index 1bce9832..00000000 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/Barcode.java +++ /dev/null @@ -1,17 +0,0 @@ -package fellowship.mealmaestro.models.mongo; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class Barcode { - String barcode; - - public Barcode() { - } - - public Barcode(String barcode) { - this.barcode = barcode; - } -} diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java index 8dc7e542..ca0401d8 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java @@ -13,6 +13,8 @@ public class FoodModelM { @Id private String barcode; + private String store; + private String name; private double quantity; diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/findBarcodeRequest.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/findBarcodeRequest.java new file mode 100644 index 00000000..a8bd7ffb --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/findBarcodeRequest.java @@ -0,0 +1,19 @@ +package fellowship.mealmaestro.models.mongo; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class findBarcodeRequest { + String barcode; + String store; + + public findBarcodeRequest() { + } + + public findBarcodeRequest(String barcode, String store) { + this.barcode = barcode; + this.store = store; + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepository.java new file mode 100644 index 00000000..3c5b8ba8 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepository.java @@ -0,0 +1,12 @@ +package fellowship.mealmaestro.repositories.mongo; + +import java.util.Optional; + +import fellowship.mealmaestro.models.mongo.FoodModelM; + +public interface DynamicFoodMRepository { + FoodModelM saveInDynamicCollection(FoodModelM food); + + // find + Optional findInDynamicCollection(String barcode, String store); +} \ No newline at end of file diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java new file mode 100644 index 00000000..720216a3 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java @@ -0,0 +1,28 @@ +package fellowship.mealmaestro.repositories.mongo; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; + +import fellowship.mealmaestro.models.mongo.FoodModelM; + +public class DynamicFoodMRepositoryImpl implements DynamicFoodMRepository { + private final MongoTemplate mongoTemplate; + + @Autowired + public DynamicFoodMRepositoryImpl(MongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } + + @Override + public FoodModelM saveInDynamicCollection(FoodModelM food) { + return mongoTemplate.save(food, food.getStore()); + } + + @Override + public Optional findInDynamicCollection(String barcode, String store) { + FoodModelM m = mongoTemplate.findById(barcode, FoodModelM.class, store); + return Optional.ofNullable(m); + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java index 71ebbd97..b35f876b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/FoodMRepository.java @@ -4,6 +4,6 @@ import fellowship.mealmaestro.models.mongo.FoodModelM; -public interface FoodMRepository extends MongoRepository { +public interface FoodMRepository extends MongoRepository, DynamicFoodMRepository { FoodModelM findByBarcode(String barcode); } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java b/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java index 727ed20b..e36c10c3 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.mongo.FoodModelM; +import fellowship.mealmaestro.models.mongo.findBarcodeRequest; import fellowship.mealmaestro.repositories.mongo.FoodMRepository; @Service @@ -14,9 +15,10 @@ public class BarcodeService { @Autowired private FoodMRepository foodMRepository; - public FoodModelM findProduct(String code) { - System.out.println(code); - Optional product = foodMRepository.findById(code); + public FoodModelM findProduct(findBarcodeRequest request) { + System.out.println(request.getStore()); + Optional product = foodMRepository.findInDynamicCollection(request.getBarcode(), + request.getStore()); if (product.isEmpty()) { FoodModelM nullProduct = new FoodModelM(); nullProduct.setName(""); @@ -29,6 +31,6 @@ public FoodModelM findProduct(String code) { } public FoodModelM addProduct(FoodModelM product) { - return foodMRepository.save(product); + return foodMRepository.saveInDynamicCollection(product); } } diff --git a/frontend/src/app/pages/pantry/pantry.page.ts b/frontend/src/app/pages/pantry/pantry.page.ts index d19f73f2..5e37201f 100644 --- a/frontend/src/app/pages/pantry/pantry.page.ts +++ b/frontend/src/app/pages/pantry/pantry.page.ts @@ -496,13 +496,17 @@ export class PantryPage implements OnInit, ViewWillEnter { next: (response) => { if (response.status === 200) { if (response.body) { - this.newItem = { - name: response.body.name, - quantity: response.body.quantity, - unit: response.body.unit, - price: response.body.price, - }; - this.modal.present(); + if (response.body.name === '') { + this.barcodeNotFound(code); + } else { + this.newItem = { + name: response.body.name, + quantity: response.body.quantity, + unit: response.body.unit, + price: response.body.price, + }; + this.modal.present(); + } } } }, @@ -522,4 +526,8 @@ export class PantryPage implements OnInit, ViewWillEnter { }, }); } + + async barcodeNotFound(code: string) { + //IMPLEMENT + } } From ecbea6089a370210608bee57ce030b95f490e73c Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Fri, 8 Sep 2023 19:22:31 +0200 Subject: [PATCH 05/97] =?UTF-8?q?=F0=9F=9A=A7=20Added=20methods=20to=20cho?= =?UTF-8?q?ose=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/pages/pantry/pantry.page.ts | 165 ++++++++++++++---- .../authentication/authentication.service.ts | 2 +- .../barcode-api/barcode-api.service.ts | 14 +- .../src/app/services/login/login.service.ts | 20 +++ .../meal-generation.service.ts | 2 +- .../services/pantry-api/pantry-api.service.ts | 2 +- .../recipe-book/recipe-book-api.service.ts | 2 +- .../shopping-list-api.service.ts | 2 +- .../app/services/user-api/user-api.service.ts | 2 +- 9 files changed, 169 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/pages/pantry/pantry.page.ts b/frontend/src/app/pages/pantry/pantry.page.ts index 5e37201f..4ed6c6de 100644 --- a/frontend/src/app/pages/pantry/pantry.page.ts +++ b/frontend/src/app/pages/pantry/pantry.page.ts @@ -5,7 +5,13 @@ import { ViewChildren, ViewChild, } from '@angular/core'; -import { IonModal, IonicModule, ViewWillEnter } from '@ionic/angular'; +import { + AlertController, + AlertInput, + IonModal, + IonicModule, + ViewWillEnter, +} from '@ionic/angular'; import { Router } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @@ -45,6 +51,7 @@ export class PantryPage implements OnInit, ViewWillEnter { shoppingItems: FoodItemI[] = []; searchTerm: string = ''; currentSort: string = 'name-down'; + stores: string[] = []; newItem: FoodItemI = { name: '', quantity: null, @@ -58,7 +65,8 @@ export class PantryPage implements OnInit, ViewWillEnter { private errorHandlerService: ErrorHandlerService, private auth: AuthenticationService, private loginService: LoginService, - private barcodeApiService: BarcodeApiService + private barcodeApiService: BarcodeApiService, + private alertController: AlertController ) {} async ngOnInit() { @@ -72,6 +80,18 @@ export class PantryPage implements OnInit, ViewWillEnter { this.fetchItems(); this.loginService.setPantryRefreshed(true); } + if (!this.loginService.isStoresRefreshed()) { + this.barcodeApiService.fetchStores().subscribe({ + next: (response) => { + if (response.status === 200) { + if (response.body) { + this.stores = response.body; + } + } + }, + }); + this.loginService.setStoresRefreshed(true); + } } async fetchItems() { @@ -480,7 +500,11 @@ export class PantryPage implements OnInit, ViewWillEnter { ], }; - this.sendBarcode(result); + if (this.loginService.isShoppingAt() === '') { + this.askShoppingLocation(result); + } else { + this.sendBarcode(result); + } } async requestPermissions(): Promise { @@ -490,44 +514,117 @@ export class PantryPage implements OnInit, ViewWillEnter { async sendBarcode(result: any): Promise { // replace any with ScanResult + console.log(result.barcodes[0].displayValue); let code = result.barcodes[0].displayValue; - this.barcodeApiService.findProduct(code).subscribe({ - next: (response) => { - if (response.status === 200) { - if (response.body) { - if (response.body.name === '') { - this.barcodeNotFound(code); - } else { - this.newItem = { - name: response.body.name, - quantity: response.body.quantity, - unit: response.body.unit, - price: response.body.price, - }; - this.modal.present(); + this.barcodeApiService + .findProduct(code, this.loginService.isShoppingAt()) + .subscribe({ + next: (response) => { + if (response.status === 200) { + if (response.body) { + if (response.body.name === '') { + this.barcodeNotFound(code); + } else { + this.newItem = { + name: response.body.name, + quantity: response.body.quantity, + unit: response.body.unit, + price: response.body.price, + }; + this.modal.present(); + } } } - } - }, - error: (err) => { - if (err.status === 403) { - this.errorHandlerService.presentErrorToast( - 'Unauthorized access. Please login again.', - err - ); - this.auth.logout(); - } else { - this.errorHandlerService.presentErrorToast( - 'Error finding product', - err - ); - } - }, - }); + }, + error: (err) => { + if (err.status === 403) { + this.errorHandlerService.presentErrorToast( + 'Unauthorized access. Please login again.', + err + ); + this.auth.logout(); + } else { + this.errorHandlerService.presentErrorToast( + 'Error finding product', + err + ); + } + }, + }); } async barcodeNotFound(code: string) { //IMPLEMENT } + + async askShoppingLocation(code: any) { + const alert = await this.alertController.create({ + header: 'Shopping Location', + message: 'Where are you shopping?', + inputs: [ + ...this.stores.map((store) => ({ + label: store, + type: 'radio' as const, + value: store, + })), + { + label: 'Other', + type: 'radio', + value: 'Other', + }, + ], + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Confirm', + handler: (data) => { + if (data === 'Other') { + this.askNewShoppingLocation(code); + return; + } + this.loginService.setShoppingAt(data); + this.sendBarcode(code); + }, + }, + ], + }); + + await alert.present(); + } + + async askNewShoppingLocation(code: any) { + const alert = await this.alertController.create({ + header: 'Shopping Location', + message: 'Where are you shopping?', + inputs: [ + { + name: 'store', + type: 'text', + placeholder: 'Store', + }, + ], + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Confirm', + handler: (data) => { + //Need to do some error handling here + + this.loginService.setShoppingAt(data.store); + this.loginService.setStoresRefreshed(false); + this.sendBarcode(code); + }, + }, + ], + }); + + await alert.present(); + } } diff --git a/frontend/src/app/services/authentication/authentication.service.ts b/frontend/src/app/services/authentication/authentication.service.ts index cef83066..f141a48a 100644 --- a/frontend/src/app/services/authentication/authentication.service.ts +++ b/frontend/src/app/services/authentication/authentication.service.ts @@ -10,7 +10,7 @@ import { LoginService } from '../login/login.service'; providedIn: 'root', }) export class AuthenticationService { - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor( private http: HttpClient, diff --git a/frontend/src/app/services/barcode-api/barcode-api.service.ts b/frontend/src/app/services/barcode-api/barcode-api.service.ts index db9685d9..9112d9a2 100644 --- a/frontend/src/app/services/barcode-api/barcode-api.service.ts +++ b/frontend/src/app/services/barcode-api/barcode-api.service.ts @@ -7,17 +7,27 @@ import { FoodItemI } from '../../models/interfaces'; providedIn: 'root', }) export class BarcodeApiService { - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor(private http: HttpClient) {} - findProduct(barcode: String): Observable> { + findProduct( + barcode: string, + store: string + ): Observable> { return this.http.post( this.url + '/findProduct', { barcode: barcode, + store: store, }, { observe: 'response' } ); } + + fetchStores(): Observable> { + return this.http.get(this.url + '/fetchStores', { + observe: 'response', + }); + } } diff --git a/frontend/src/app/services/login/login.service.ts b/frontend/src/app/services/login/login.service.ts index 5c9ed999..70f47bb3 100644 --- a/frontend/src/app/services/login/login.service.ts +++ b/frontend/src/app/services/login/login.service.ts @@ -8,6 +8,8 @@ export class LoginService { private pantryRefreshed: boolean = false; private recipeBookRefreshed: boolean = false; private settingsRefreshed: boolean = false; + private storesRefreshed: boolean = false; + private shoppingLocation: string | '' = ''; constructor() {} @@ -43,11 +45,29 @@ export class LoginService { this.settingsRefreshed = refreshed; } + isStoresRefreshed(): boolean { + return this.storesRefreshed; + } + + setStoresRefreshed(refreshed: boolean): void { + this.storesRefreshed = refreshed; + } + + isShoppingAt(): string { + return this.shoppingLocation; + } + + setShoppingAt(location: string): void { + this.shoppingLocation = location; + } + resetRefreshed(): void { this.homeRefreshed = false; this.pantryRefreshed = false; this.recipeBookRefreshed = false; this.settingsRefreshed = false; + this.storesRefreshed = false; + this.shoppingLocation = ''; } toString(): string { diff --git a/frontend/src/app/services/meal-generation/meal-generation.service.ts b/frontend/src/app/services/meal-generation/meal-generation.service.ts index 1c3b1462..14ad361f 100644 --- a/frontend/src/app/services/meal-generation/meal-generation.service.ts +++ b/frontend/src/app/services/meal-generation/meal-generation.service.ts @@ -7,7 +7,7 @@ import { MealI, RegenerateMealRequestI } from '../../models/interfaces'; providedIn: 'root', }) export class MealGenerationService { - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor(private http: HttpClient) {} diff --git a/frontend/src/app/services/pantry-api/pantry-api.service.ts b/frontend/src/app/services/pantry-api/pantry-api.service.ts index 761dd275..1a17228c 100644 --- a/frontend/src/app/services/pantry-api/pantry-api.service.ts +++ b/frontend/src/app/services/pantry-api/pantry-api.service.ts @@ -7,7 +7,7 @@ import { FoodItemI } from '../../models/interfaces'; providedIn: 'root', }) export class PantryApiService { - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor(private http: HttpClient) {} diff --git a/frontend/src/app/services/recipe-book/recipe-book-api.service.ts b/frontend/src/app/services/recipe-book/recipe-book-api.service.ts index 2ed0a874..2f44dd01 100644 --- a/frontend/src/app/services/recipe-book/recipe-book-api.service.ts +++ b/frontend/src/app/services/recipe-book/recipe-book-api.service.ts @@ -13,7 +13,7 @@ export class RecipeBookApiService { password: '', }; - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor(private http: HttpClient) {} diff --git a/frontend/src/app/services/shopping-list-api/shopping-list-api.service.ts b/frontend/src/app/services/shopping-list-api/shopping-list-api.service.ts index 112b9452..8fe447fe 100644 --- a/frontend/src/app/services/shopping-list-api/shopping-list-api.service.ts +++ b/frontend/src/app/services/shopping-list-api/shopping-list-api.service.ts @@ -7,7 +7,7 @@ import { FoodItemI } from '../../models/interfaces'; providedIn: 'root', }) export class ShoppingListApiService { - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor(private http: HttpClient) {} diff --git a/frontend/src/app/services/user-api/user-api.service.ts b/frontend/src/app/services/user-api/user-api.service.ts index 11207342..acbcbc8c 100644 --- a/frontend/src/app/services/user-api/user-api.service.ts +++ b/frontend/src/app/services/user-api/user-api.service.ts @@ -7,7 +7,7 @@ import { UserI } from '../../models/user.model'; providedIn: 'root', }) export class UserApiService { - url: String = 'http://localhost:8080'; + url: string = 'http://localhost:8080'; constructor(private http: HttpClient) {} From 5379238f90180eb1c70cb8750ddc55b4acf15e6a Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sat, 9 Sep 2023 13:29:17 +0200 Subject: [PATCH 06/97] =?UTF-8?q?=F0=9F=9A=A7=20Webscraper=20code=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 + .../mealmaestro/MealmaestroApplication.java | 4 +- .../mealmaestro/models/mongo/FoodModelM.java | 21 +- .../services/webscraping/CheckersScraper.java | 250 ++++++++++++++++++ .../webscraping/WebscrapeService.java | 20 ++ frontend/src/app/pages/pantry/pantry.page.ts | 65 ++--- .../barcode-api/barcode-api.service.ts | 6 - .../src/app/services/login/login.service.ts | 10 - 8 files changed, 317 insertions(+), 61 deletions(-) create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java diff --git a/backend/build.gradle b/backend/build.gradle index 85154fed..a3d59903 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -25,6 +25,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-neo4j' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.seleniumhq.selenium:selenium-java:4.12.1' + implementation 'org.jsoup:jsoup:1.16.1' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' diff --git a/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java b/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java index 6f094f5f..bd66dedc 100644 --- a/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java +++ b/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java @@ -1,13 +1,13 @@ package fellowship.mealmaestro; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; import com.fasterxml.jackson.core.JsonProcessingException; - @SpringBootApplication +@EnableScheduling public class MealmaestroApplication { public static void main(String[] args) throws JsonProcessingException { diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java index ca0401d8..0078ba7f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java @@ -1,5 +1,8 @@ package fellowship.mealmaestro.models.mongo; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @@ -41,4 +44,20 @@ public String toString() { price + ", quantity=" + quantity + ", unit=" + unit + "]"; } -} + + public void setPrice(String price) { + // remove R sign + price = price.substring(1); + this.price = Double.parseDouble(price); + } + + public void setAmount(String amount) { + Pattern pattern = Pattern.compile("([0-9.]+)([a-zA-Z]+)"); + Matcher matcher = pattern.matcher(amount); + + if (matcher.find()) { + this.quantity = Double.parseDouble(matcher.group(1)); + this.unit = matcher.group(2); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java new file mode 100644 index 00000000..2bf276e4 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -0,0 +1,250 @@ +package fellowship.mealmaestro.services.webscraping; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import fellowship.mealmaestro.models.mongo.FoodModelM; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +public class CheckersScraper { + + public void scrape() { + System.setProperty("webdriver.chrome.driver", + "C:\\Users\\Ethan\\Downloads\\chromedriver-win64\\chromedriver-win64\\chromedriver.exe"); + + ChromeOptions options = new ChromeOptions(); + // options.addArguments("--headless"); + options.addArguments("--blink-settings=imagesEnabled=false"); + options.addArguments("--disable-extensions"); + options.addArguments("--user-agent=FellowshipBot-UniversityOfPretoria-FinalYearProject"); + + WebDriver driver = new ChromeDriver(options); + Duration timeout = Duration.ofSeconds(15); + WebDriverWait wait = new WebDriverWait(driver, timeout); + + // Visit categories sitemap to get all locs + // driver.get("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); + + // String domString = driver.getPageSource(); + + // Document doc = Jsoup.parse(domString); + // Elements links = doc.select("loc"); + + // Filter out non-food links + List foodLinks = new ArrayList(); + // for (int i = 0; i < links.size(); i++) { + // String link = links.get(i).text(); + // if (link.contains("food") || link.contains("Food")) { + // foodLinks.add(link); + // } + // } + + long lastRequestTime = 0; + + foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\applep1.html"); + foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\milk.html"); + + // Visit each food category page and get all product links + Set visitedLinks = new HashSet(); + List productLinks = new ArrayList(); + List paginationLinks = new ArrayList(); + List foodModels = new ArrayList(); + + for (String link : foodLinks) { + // Check if link has been visited + if (visitedLinks.contains(link)) { + continue; + } + + long currentTime = System.currentTimeMillis(); + long timeSinceLastRequest = currentTime - lastRequestTime; + + // Wait 10 seconds between requests + if (timeSinceLastRequest < 10000) { + try { + System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); + Thread.sleep(10000 - timeSinceLastRequest); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + visitedLinks.add(link); + driver.get(link); + System.out.println("Visiting " + link); + + // Wait for page to load + WebElement element = wait + .until(ExpectedConditions.elementToBeClickable(By.cssSelector("h3.item-product__name > a"))); + + // Full products page dom + String dom = driver.getPageSource(); + Document productPageDoc = Jsoup.connect(link).get(); + + // Get product links + Elements productPageLinks = productPageDoc.select("h3.item-product__name > a"); + + for (int i = 0; i < productPageLinks.size(); i++) { + String productPageLink = productPageLinks.get(i).attr("href"); + productLinks.add(productPageLink); + } + + // Find pagination links if they exist + Element paginationBar = productPageDoc.selectFirst("div.pagination-bar.bottom"); + Elements paginationLinksEl = paginationBar.select("a"); + + for (Element paginationLink : paginationLinksEl) { + String paginationLinkHref = paginationLink.attr("href"); + + // add to pagination links if they aren't in visitedLinks + if (!visitedLinks.contains(paginationLinkHref)) { + paginationLinks.add(paginationLinkHref); + } + } + + // Update last request time + lastRequestTime = System.currentTimeMillis(); + } + + System.out.println("############################################"); + System.out.println("Main links finished, starting pagination links"); + System.out.println("############################################"); + + // Visit each pagination link and get all product links + for (String link : paginationLinks) { + // Check if link has been visited + if (visitedLinks.contains(link)) { + continue; + } + + long currentTime = System.currentTimeMillis(); + long timeSinceLastRequest = currentTime - lastRequestTime; + + // Wait 10 seconds between requests + if (timeSinceLastRequest < 10000) { + try { + System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); + Thread.sleep(10000 - timeSinceLastRequest); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + visitedLinks.add(link); + driver.get("file:///D:\\Code\\MessingAround" + link); + System.out.println("Visiting " + link); + + // Wait for page to load + WebElement element = wait + .until(ExpectedConditions.elementToBeClickable(By.cssSelector("h3.item-product__name > a"))); + + // Full products page dom + String dom = driver.getPageSource(); + Document productPageDoc = Jsoup.parse(dom); + + // Get product links + Elements productPageLinks = productPageDoc.select("h3.item-product__name > a"); + + for (int i = 0; i < productPageLinks.size(); i++) { + String productPageLink = productPageLinks.get(i).attr("href"); + productLinks.add(productPageLink); + } + + // Update last request time + lastRequestTime = System.currentTimeMillis(); + } + + System.out.println("############################################"); + System.out.println("Finished getting product links"); + System.out.println("############################################"); + + // Visit each product page and get product info + for (String link : productLinks) { + // Check if link has been visited + if (visitedLinks.contains(link)) { + continue; + } + long currentTime = System.currentTimeMillis(); + long timeSinceLastRequest = currentTime - lastRequestTime; + + // Wait 10 seconds between requests + if (timeSinceLastRequest < 10000) { + try { + System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); + Thread.sleep(10000 - timeSinceLastRequest); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + visitedLinks.add(link); + driver.get("file:///D:\\Code\\MessingAround" + link); + System.out.println("Visiting " + driver.getTitle()); + + // Wait for page to load + WebElement element = wait.until( + ExpectedConditions.elementToBeClickable(By.cssSelector("#accessibletabsnavigation0-1"))); + + // Full product page dom + String dom = driver.getPageSource(); + Document productDoc = Jsoup.parse(dom); + + // Get product info + FoodModelM food = new FoodModelM(); + // product name + String productName = productDoc.selectFirst("h1.pdp__name").text(); + System.out.println("Product name: " + productName); + food.setName(productName); + + // product price + String productPrice = productDoc.selectFirst("div.special-price__price").text(); + System.out.println("Product price: " + productPrice); + food.setPrice(productPrice); + + // product details + Elements productDetails = productDoc.select("table.pdp__product-information > tbody > tr"); + + String barcode = ""; + String quantity = ""; + + for (Element productDetail : productDetails) { + if (productDetail.text().toLowerCase().contains("barcode")) { + // select second td + barcode = productDetail.selectFirst("td:nth-child(2)").text(); + System.out.println("Barcode: " + barcode); + food.setBarcode(barcode); + } + + if (productDetail.text().toLowerCase().contains("weight") + || productDetail.text().toLowerCase().contains("volume")) { + // select second td + quantity = productDetail.selectFirst("td:nth-child(2)").text(); + System.out.println("Quantity: " + quantity); + food.setAmount(quantity); + } + } + + // Add food to list + foodModels.add(food); + + lastRequestTime = System.currentTimeMillis(); + } + + driver.quit(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java new file mode 100644 index 00000000..a2abc472 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java @@ -0,0 +1,20 @@ +package fellowship.mealmaestro.services.webscraping; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +public class WebscrapeService { + + private volatile boolean isScrapingAllowed = true; + + @Scheduled(cron = "0 0 6 * * ?") + public void startScrape() { + System.out.println("Scraping started..."); + } + + @Scheduled(cron = "0 42 10 * * ?") + public void stopScraping() { + System.out.println("Scraping stopped..."); + } +} diff --git a/frontend/src/app/pages/pantry/pantry.page.ts b/frontend/src/app/pages/pantry/pantry.page.ts index 4ed6c6de..97b58055 100644 --- a/frontend/src/app/pages/pantry/pantry.page.ts +++ b/frontend/src/app/pages/pantry/pantry.page.ts @@ -51,7 +51,6 @@ export class PantryPage implements OnInit, ViewWillEnter { shoppingItems: FoodItemI[] = []; searchTerm: string = ''; currentSort: string = 'name-down'; - stores: string[] = []; newItem: FoodItemI = { name: '', quantity: null, @@ -80,18 +79,6 @@ export class PantryPage implements OnInit, ViewWillEnter { this.fetchItems(); this.loginService.setPantryRefreshed(true); } - if (!this.loginService.isStoresRefreshed()) { - this.barcodeApiService.fetchStores().subscribe({ - next: (response) => { - if (response.status === 200) { - if (response.body) { - this.stores = response.body; - } - } - }, - }); - this.loginService.setStoresRefreshed(true); - } } async fetchItems() { @@ -524,7 +511,7 @@ export class PantryPage implements OnInit, ViewWillEnter { if (response.status === 200) { if (response.body) { if (response.body.name === '') { - this.barcodeNotFound(code); + this.barcodeNotFound(); } else { this.newItem = { name: response.body.name, @@ -554,24 +541,16 @@ export class PantryPage implements OnInit, ViewWillEnter { }); } - async barcodeNotFound(code: string) { + async barcodeNotFound() { //IMPLEMENT - } - - async askShoppingLocation(code: any) { const alert = await this.alertController.create({ - header: 'Shopping Location', - message: 'Where are you shopping?', + header: 'Barcode not found', + message: 'Please enter the name of the item', inputs: [ - ...this.stores.map((store) => ({ - label: store, - type: 'radio' as const, - value: store, - })), { - label: 'Other', - type: 'radio', - value: 'Other', + name: 'name', + type: 'text', + placeholder: 'Name', }, ], buttons: [ @@ -582,12 +561,12 @@ export class PantryPage implements OnInit, ViewWillEnter { { text: 'Confirm', handler: (data) => { - if (data === 'Other') { - this.askNewShoppingLocation(code); - return; - } - this.loginService.setShoppingAt(data); - this.sendBarcode(code); + this.newItem = { + name: data.name, + quantity: null, + unit: 'pcs', + }; + this.modal.present(); }, }, ], @@ -596,15 +575,20 @@ export class PantryPage implements OnInit, ViewWillEnter { await alert.present(); } - async askNewShoppingLocation(code: any) { + async askShoppingLocation(code: any) { const alert = await this.alertController.create({ header: 'Shopping Location', message: 'Where are you shopping?', inputs: [ { - name: 'store', - type: 'text', - placeholder: 'Store', + label: 'Woolworths', + type: 'radio', + value: 'Woolworths', + }, + { + label: 'Checkers', + type: 'radio', + value: 'Checkers', }, ], buttons: [ @@ -615,10 +599,7 @@ export class PantryPage implements OnInit, ViewWillEnter { { text: 'Confirm', handler: (data) => { - //Need to do some error handling here - - this.loginService.setShoppingAt(data.store); - this.loginService.setStoresRefreshed(false); + this.loginService.setShoppingAt(data); this.sendBarcode(code); }, }, diff --git a/frontend/src/app/services/barcode-api/barcode-api.service.ts b/frontend/src/app/services/barcode-api/barcode-api.service.ts index 9112d9a2..ef8ce40d 100644 --- a/frontend/src/app/services/barcode-api/barcode-api.service.ts +++ b/frontend/src/app/services/barcode-api/barcode-api.service.ts @@ -24,10 +24,4 @@ export class BarcodeApiService { { observe: 'response' } ); } - - fetchStores(): Observable> { - return this.http.get(this.url + '/fetchStores', { - observe: 'response', - }); - } } diff --git a/frontend/src/app/services/login/login.service.ts b/frontend/src/app/services/login/login.service.ts index 70f47bb3..2c090226 100644 --- a/frontend/src/app/services/login/login.service.ts +++ b/frontend/src/app/services/login/login.service.ts @@ -8,7 +8,6 @@ export class LoginService { private pantryRefreshed: boolean = false; private recipeBookRefreshed: boolean = false; private settingsRefreshed: boolean = false; - private storesRefreshed: boolean = false; private shoppingLocation: string | '' = ''; constructor() {} @@ -45,14 +44,6 @@ export class LoginService { this.settingsRefreshed = refreshed; } - isStoresRefreshed(): boolean { - return this.storesRefreshed; - } - - setStoresRefreshed(refreshed: boolean): void { - this.storesRefreshed = refreshed; - } - isShoppingAt(): string { return this.shoppingLocation; } @@ -66,7 +57,6 @@ export class LoginService { this.pantryRefreshed = false; this.recipeBookRefreshed = false; this.settingsRefreshed = false; - this.storesRefreshed = false; this.shoppingLocation = ''; } From 292f8ddfadca550f46202957d29c84e19ea3e1a2 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sat, 9 Sep 2023 14:46:52 +0200 Subject: [PATCH 07/97] =?UTF-8?q?=F0=9F=9A=A7=20Get=20sitemap=20links=20Ch?= =?UTF-8?q?eckers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/mongo/ToVisitLinkModel.java | 22 ++++++++ .../mongo/ToVisitLinkRepository.java | 10 ++++ .../services/webscraping/CheckersScraper.java | 54 ++++++++++++++----- 3 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java new file mode 100644 index 00000000..9d60d49b --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java @@ -0,0 +1,22 @@ +package fellowship.mealmaestro.models.mongo; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import lombok.Getter; +import lombok.Setter; + +@Document(collection = "ToVisitLinks") +@Setter +@Getter +public class ToVisitLinkModel { + @Id + private String link; + + public ToVisitLinkModel(String link) { + this.link = link; + } + + public ToVisitLinkModel() { + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java new file mode 100644 index 00000000..d7b903ce --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java @@ -0,0 +1,10 @@ +package fellowship.mealmaestro.repositories.mongo; + +import org.springframework.data.mongodb.repository.MongoRepository; + +import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; + +public interface ToVisitLinkRepository extends + MongoRepository { + +} diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 2bf276e4..1523db19 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -7,13 +7,20 @@ import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; +import org.springframework.beans.factory.annotation.Autowired; import fellowship.mealmaestro.models.mongo.FoodModelM; +import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; +import fellowship.mealmaestro.models.mongo.VisitedLinkModel; +import fellowship.mealmaestro.repositories.mongo.ToVisitLinkRepository; +import fellowship.mealmaestro.repositories.mongo.VisitedLinkRepository; +import fellowship.mealmaestro.services.BarcodeService; import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.jsoup.Jsoup; @@ -22,20 +29,43 @@ import org.jsoup.select.Elements; public class CheckersScraper { + @Autowired + private ToVisitLinkRepository toVisitLinkRepository; + + @Autowired + private VisitedLinkRepository visitedLinkRepository; + + @Autowired + private BarcodeService barcodeService; + + public void getLocLinks() { + // Visit categories sitemap to get all locs + Optional visited = visitedLinkRepository + .findById("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); + + if (visited.isPresent()) { + System.out.println("Skipping sitemap, already visited"); + return; + } + + try { + Document doc = Jsoup.connect("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml").get(); + + Elements links = doc.select("loc"); + + // Filter out non-food links + for (int i = 0; i < links.size(); i++) { + String link = links.get(i).text(); + if (link.contains("food") || link.contains("Food")) { + toVisitLinkRepository.save(new ToVisitLinkModel(link)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } public void scrape() { - System.setProperty("webdriver.chrome.driver", - "C:\\Users\\Ethan\\Downloads\\chromedriver-win64\\chromedriver-win64\\chromedriver.exe"); - - ChromeOptions options = new ChromeOptions(); - // options.addArguments("--headless"); - options.addArguments("--blink-settings=imagesEnabled=false"); - options.addArguments("--disable-extensions"); - options.addArguments("--user-agent=FellowshipBot-UniversityOfPretoria-FinalYearProject"); - - WebDriver driver = new ChromeDriver(options); - Duration timeout = Duration.ofSeconds(15); - WebDriverWait wait = new WebDriverWait(driver, timeout); // Visit categories sitemap to get all locs // driver.get("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); From 69b7cfcb4c6667b412fab011177e53b60400f331 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sat, 9 Sep 2023 15:14:08 +0200 Subject: [PATCH 08/97] =?UTF-8?q?=F0=9F=9A=A7=20Logic=20to=20process=20lin?= =?UTF-8?q?ks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/mongo/ToVisitLinkModel.java | 5 ++- .../models/mongo/VisitedLinkModel.java | 11 +++++- .../mongo/ToVisitLinkRepository.java | 6 +++- .../services/webscraping/CheckersScraper.java | 36 ++++++++----------- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java index 9d60d49b..eb705e0b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java @@ -13,8 +13,11 @@ public class ToVisitLinkModel { @Id private String link; - public ToVisitLinkModel(String link) { + private String type; + + public ToVisitLinkModel(String link, String type) { this.link = link; + this.type = type; } public ToVisitLinkModel() { diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java index 9a2ea0be..9a5bc355 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java @@ -17,9 +17,18 @@ public class VisitedLinkModel { private LocalDate lastVisited; - public VisitedLinkModel(String link, LocalDate lastVisited) { + private String type; + + public VisitedLinkModel(String link, LocalDate lastVisited, String type) { this.link = link; this.lastVisited = lastVisited; + this.type = type; + } + + public VisitedLinkModel(String link, String type) { + this.link = link; + this.type = type; + this.lastVisited = LocalDate.now(); } public VisitedLinkModel() { diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java index d7b903ce..09996678 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java @@ -1,10 +1,14 @@ package fellowship.mealmaestro.repositories.mongo; +import java.util.Optional; + import org.springframework.data.mongodb.repository.MongoRepository; import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; public interface ToVisitLinkRepository extends - MongoRepository { + MongoRepository { + + Optional findNext(); // TODO: add query to find next link } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 1523db19..885ed817 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -38,8 +38,11 @@ public class CheckersScraper { @Autowired private BarcodeService barcodeService; + private long lastRequestTime; + public void getLocLinks() { // Visit categories sitemap to get all locs + lastRequestTime = 0; Optional visited = visitedLinkRepository .findById("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); @@ -57,7 +60,7 @@ public void getLocLinks() { for (int i = 0; i < links.size(); i++) { String link = links.get(i).text(); if (link.contains("food") || link.contains("Food")) { - toVisitLinkRepository.save(new ToVisitLinkModel(link)); + toVisitLinkRepository.save(new ToVisitLinkModel(link, "category")); } } } catch (Exception e) { @@ -65,32 +68,23 @@ public void getLocLinks() { } } - public void scrape() { - - // Visit categories sitemap to get all locs - // driver.get("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); - - // String domString = driver.getPageSource(); + public ToVisitLinkModel getNextLink() { + // Get next link to visit + Optional toVisitLink = toVisitLinkRepository.findNext(); - // Document doc = Jsoup.parse(domString); - // Elements links = doc.select("loc"); + if (toVisitLink.isPresent()) { + return toVisitLink.get(); + } - // Filter out non-food links - List foodLinks = new ArrayList(); - // for (int i = 0; i < links.size(); i++) { - // String link = links.get(i).text(); - // if (link.contains("food") || link.contains("Food")) { - // foodLinks.add(link); - // } - // } + return null; + } - long lastRequestTime = 0; + public void scrape() { - foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\applep1.html"); - foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\milk.html"); + // foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\applep1.html"); + // foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\milk.html"); // Visit each food category page and get all product links - Set visitedLinks = new HashSet(); List productLinks = new ArrayList(); List paginationLinks = new ArrayList(); List foodModels = new ArrayList(); From 0930af56da83c07bff8c467fde73ceb12dbdc168 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sun, 10 Sep 2023 13:52:51 +0200 Subject: [PATCH 09/97] =?UTF-8?q?=F0=9F=9A=A7=20Method=20to=20handle=20cat?= =?UTF-8?q?egory=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/mongo/ToVisitLinkModel.java | 5 +- .../models/mongo/VisitedLinkModel.java | 8 +- .../mongo/ToVisitLinkRepository.java | 4 - .../services/webscraping/CheckersScraper.java | 164 +++++++----------- .../services/webscraping/LinkService.java | 31 ++++ 5 files changed, 106 insertions(+), 106 deletions(-) create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java index eb705e0b..a731db2f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java @@ -15,9 +15,12 @@ public class ToVisitLinkModel { private String type; - public ToVisitLinkModel(String link, String type) { + private String store; + + public ToVisitLinkModel(String link, String type, String store) { this.link = link; this.type = type; + this.store = store; } public ToVisitLinkModel() { diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java index 9a5bc355..b02d27c8 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/VisitedLinkModel.java @@ -19,16 +19,20 @@ public class VisitedLinkModel { private String type; - public VisitedLinkModel(String link, LocalDate lastVisited, String type) { + private String store; + + public VisitedLinkModel(String link, LocalDate lastVisited, String type, String store) { this.link = link; this.lastVisited = lastVisited; this.type = type; + this.store = store; } - public VisitedLinkModel(String link, String type) { + public VisitedLinkModel(String link, String type, String store) { this.link = link; this.type = type; this.lastVisited = LocalDate.now(); + this.store = store; } public VisitedLinkModel() { diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java index 09996678..88b4a375 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/ToVisitLinkRepository.java @@ -1,7 +1,5 @@ package fellowship.mealmaestro.repositories.mongo; -import java.util.Optional; - import org.springframework.data.mongodb.repository.MongoRepository; import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; @@ -9,6 +7,4 @@ public interface ToVisitLinkRepository extends MongoRepository { - Optional findNext(); // TODO: add query to find next link - } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 885ed817..00a51fcf 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -16,7 +16,9 @@ import fellowship.mealmaestro.repositories.mongo.VisitedLinkRepository; import fellowship.mealmaestro.services.BarcodeService; +import java.io.IOException; import java.time.Duration; +import java.time.LocalDate; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -35,6 +37,9 @@ public class CheckersScraper { @Autowired private VisitedLinkRepository visitedLinkRepository; + @Autowired + private LinkService linkService; + @Autowired private BarcodeService barcodeService; @@ -60,7 +65,7 @@ public void getLocLinks() { for (int i = 0; i < links.size(); i++) { String link = links.get(i).text(); if (link.contains("food") || link.contains("Food")) { - toVisitLinkRepository.save(new ToVisitLinkModel(link, "category")); + toVisitLinkRepository.save(new ToVisitLinkModel(link, "category", "Checkers")); } } } catch (Exception e) { @@ -70,7 +75,22 @@ public void getLocLinks() { public ToVisitLinkModel getNextLink() { // Get next link to visit - Optional toVisitLink = toVisitLinkRepository.findNext(); + + long currentTime = System.currentTimeMillis(); + long timeSinceLastRequest = currentTime - lastRequestTime; + + // Wait 10 seconds between requests + if (timeSinceLastRequest < 10000) { + try { + System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); + Thread.sleep(10000 - timeSinceLastRequest); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + Optional toVisitLink = linkService.getNextLink(); + lastRequestTime = System.currentTimeMillis(); if (toVisitLink.isPresent()) { return toVisitLink.get(); @@ -79,123 +99,69 @@ public ToVisitLinkModel getNextLink() { return null; } - public void scrape() { - - // foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\applep1.html"); - // foodLinks.add("file:///D:\\Code\\MessingAround\\app\\src\\main\\java\\messingaround\\milk.html"); - - // Visit each food category page and get all product links - List productLinks = new ArrayList(); - List paginationLinks = new ArrayList(); - List foodModels = new ArrayList(); - - for (String link : foodLinks) { - // Check if link has been visited - if (visitedLinks.contains(link)) { - continue; - } - - long currentTime = System.currentTimeMillis(); - long timeSinceLastRequest = currentTime - lastRequestTime; - - // Wait 10 seconds between requests - if (timeSinceLastRequest < 10000) { - try { - System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); - Thread.sleep(10000 - timeSinceLastRequest); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } + public void handleLink(ToVisitLinkModel toVisitLink) { + // Handle link based on type + if (toVisitLink.getType().equals("category")) { + handleCategoryLink(toVisitLink); + } else if (toVisitLink.getType().equals("product")) { + handleProductLink(toVisitLink); + } + } - visitedLinks.add(link); - driver.get(link); - System.out.println("Visiting " + link); + public void handleCategoryLink(ToVisitLinkModel link) { + // Visit category page and get all product links and pagination links + Optional visited = visitedLinkRepository.findById(link.getLink()); - // Wait for page to load - WebElement element = wait - .until(ExpectedConditions.elementToBeClickable(By.cssSelector("h3.item-product__name > a"))); + // if link has been visited and it has been less than 1 month since last visit, + // skip + if (visited.isPresent() && visited.get().getLastVisited().plusMonths(1).isAfter(LocalDate.now())) { + System.out.println("Skipping " + link.getLink() + ", already visited..."); + return; + } - // Full products page dom - String dom = driver.getPageSource(); - Document productPageDoc = Jsoup.connect(link).get(); + try { + Document doc = Jsoup.connect("https://www.checkers.co.za" + link.getLink()).get(); // Get product links - Elements productPageLinks = productPageDoc.select("h3.item-product__name > a"); + Elements productPageLinks = doc.select("h3.item-product__name > a"); for (int i = 0; i < productPageLinks.size(); i++) { String productPageLink = productPageLinks.get(i).attr("href"); - productLinks.add(productPageLink); - } - - // Find pagination links if they exist - Element paginationBar = productPageDoc.selectFirst("div.pagination-bar.bottom"); - Elements paginationLinksEl = paginationBar.select("a"); - for (Element paginationLink : paginationLinksEl) { - String paginationLinkHref = paginationLink.attr("href"); - - // add to pagination links if they aren't in visitedLinks - if (!visitedLinks.contains(paginationLinkHref)) { - paginationLinks.add(paginationLinkHref); + if (productPageLink != null && !productPageLink.isEmpty()) { + toVisitLinkRepository.save(new ToVisitLinkModel(productPageLink, "product", "Checkers")); } } - // Update last request time - lastRequestTime = System.currentTimeMillis(); - } + // Get pagination links + Element paginationBar = doc.selectFirst("div.pagination-bar.bottom"); + if (paginationBar != null) { + Elements paginationLinksEl = paginationBar.select("a"); - System.out.println("############################################"); - System.out.println("Main links finished, starting pagination links"); - System.out.println("############################################"); - - // Visit each pagination link and get all product links - for (String link : paginationLinks) { - // Check if link has been visited - if (visitedLinks.contains(link)) { - continue; - } + for (Element paginationLink : paginationLinksEl) { + String paginationLinkHref = paginationLink.attr("href"); - long currentTime = System.currentTimeMillis(); - long timeSinceLastRequest = currentTime - lastRequestTime; + // add to ToVisitLinks if they aren't in VisitedLinks + if (!visitedLinkRepository.existsById(paginationLinkHref)) { + toVisitLinkRepository.save(new ToVisitLinkModel(paginationLinkHref, "category", "Checkers")); + } - // Wait 10 seconds between requests - if (timeSinceLastRequest < 10000) { - try { - System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); - Thread.sleep(10000 - timeSinceLastRequest); - } catch (InterruptedException e) { - e.printStackTrace(); } } - visitedLinks.add(link); - driver.get("file:///D:\\Code\\MessingAround" + link); - System.out.println("Visiting " + link); - - // Wait for page to load - WebElement element = wait - .until(ExpectedConditions.elementToBeClickable(By.cssSelector("h3.item-product__name > a"))); - - // Full products page dom - String dom = driver.getPageSource(); - Document productPageDoc = Jsoup.parse(dom); - - // Get product links - Elements productPageLinks = productPageDoc.select("h3.item-product__name > a"); - - for (int i = 0; i < productPageLinks.size(); i++) { - String productPageLink = productPageLinks.get(i).attr("href"); - productLinks.add(productPageLink); - } - - // Update last request time - lastRequestTime = System.currentTimeMillis(); + // Add link to visited links + visitedLinkRepository.save(new VisitedLinkModel(link.getLink(), "category", "Checkers")); + System.out.println("Visited " + link.getLink()); + } catch (IOException e) { + System.out.println("Error visiting " + link.getLink() + ", skipping..."); + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); } - System.out.println("############################################"); - System.out.println("Finished getting product links"); - System.out.println("############################################"); + } + + public void scrape() { // Visit each product page and get product info for (String link : productLinks) { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java new file mode 100644 index 00000000..90d9e603 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java @@ -0,0 +1,31 @@ +package fellowship.mealmaestro.services.webscraping; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.stereotype.Service; + +import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; + +@Service +public class LinkService { + + @Autowired + private MongoTemplate mongoTemplate; + + public Optional getNextLink() { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.sample(1)); + + AggregationResults results = mongoTemplate.aggregate(aggregation, "ToVisitLinks", + ToVisitLinkModel.class); + + List links = results.getMappedResults(); + + return links.isEmpty() ? Optional.empty() : Optional.of(links.get(0)); + } +} From 294fd6bd597626deb078ffc05b8fae3d857ab174 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sun, 10 Sep 2023 14:57:40 +0200 Subject: [PATCH 10/97] =?UTF-8?q?=F0=9F=9A=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/webscraping/CheckersScraper.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 00a51fcf..4e796640 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -68,6 +68,14 @@ public void getLocLinks() { toVisitLinkRepository.save(new ToVisitLinkModel(link, "category", "Checkers")); } } + + // Add sitemap to visited links + visitedLinkRepository.save(new VisitedLinkModel( + "https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml", "category", "Checkers")); + // Remove sitemap from ToVisitLinks + toVisitLinkRepository.deleteById( + "https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); + } catch (Exception e) { e.printStackTrace(); } @@ -151,6 +159,10 @@ public void handleCategoryLink(ToVisitLinkModel link) { // Add link to visited links visitedLinkRepository.save(new VisitedLinkModel(link.getLink(), "category", "Checkers")); + + // Remove link from ToVisitLinks + toVisitLinkRepository.deleteById(link.getLink()); + System.out.println("Visited " + link.getLink()); } catch (IOException e) { System.out.println("Error visiting " + link.getLink() + ", skipping..."); @@ -161,6 +173,10 @@ public void handleCategoryLink(ToVisitLinkModel link) { } + public void handleProductLink(ToVisitLinkModel link) { + + } + public void scrape() { // Visit each product page and get product info @@ -169,18 +185,6 @@ public void scrape() { if (visitedLinks.contains(link)) { continue; } - long currentTime = System.currentTimeMillis(); - long timeSinceLastRequest = currentTime - lastRequestTime; - - // Wait 10 seconds between requests - if (timeSinceLastRequest < 10000) { - try { - System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); - Thread.sleep(10000 - timeSinceLastRequest); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } visitedLinks.add(link); driver.get("file:///D:\\Code\\MessingAround" + link); From b433ecc16387e3334f2bd49b71dfa015be5c2d08 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sun, 10 Sep 2023 15:46:51 +0200 Subject: [PATCH 11/97] =?UTF-8?q?=F0=9F=9A=A7=20Method=20to=20handle=20pro?= =?UTF-8?q?duct=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mealmaestro/models/mongo/FoodModelM.java | 4 + .../services/webscraping/CheckersScraper.java | 109 +++++++++++------- .../services/webscraping/LinkService.java | 15 ++- .../webscraping/WebscrapeService.java | 6 + 4 files changed, 89 insertions(+), 45 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java index 0078ba7f..ed08d77d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java @@ -51,6 +51,10 @@ public void setPrice(String price) { this.price = Double.parseDouble(price); } + public void setPrice(Double price) { + this.price = price; + } + public void setAmount(String amount) { Pattern pattern = Pattern.compile("([0-9.]+)([a-zA-Z]+)"); Matcher matcher = pattern.matcher(amount); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 4e796640..1720fd00 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -1,12 +1,5 @@ package fellowship.mealmaestro.services.webscraping; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; import org.springframework.beans.factory.annotation.Autowired; import fellowship.mealmaestro.models.mongo.FoodModelM; @@ -17,13 +10,8 @@ import fellowship.mealmaestro.services.BarcodeService; import java.io.IOException; -import java.time.Duration; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; import java.util.Optional; -import java.util.Set; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -97,7 +85,7 @@ public ToVisitLinkModel getNextLink() { } } - Optional toVisitLink = linkService.getNextLink(); + Optional toVisitLink = linkService.getNextCheckersLink(); lastRequestTime = System.currentTimeMillis(); if (toVisitLink.isPresent()) { @@ -174,44 +162,52 @@ public void handleCategoryLink(ToVisitLinkModel link) { } public void handleProductLink(ToVisitLinkModel link) { + // Visit product page and get product info - } - - public void scrape() { - - // Visit each product page and get product info - for (String link : productLinks) { - // Check if link has been visited - if (visitedLinks.contains(link)) { - continue; - } - - visitedLinks.add(link); - driver.get("file:///D:\\Code\\MessingAround" + link); - System.out.println("Visiting " + driver.getTitle()); + // Check if link has been visited + Optional visited = visitedLinkRepository.findById(link.getLink()); - // Wait for page to load - WebElement element = wait.until( - ExpectedConditions.elementToBeClickable(By.cssSelector("#accessibletabsnavigation0-1"))); + if (visited.isPresent() && visited.get().getLastVisited().plusMonths(1).isAfter(LocalDate.now())) { + System.out.println("Skipping " + link.getLink() + ", already visited..."); + return; + } - // Full product page dom - String dom = driver.getPageSource(); - Document productDoc = Jsoup.parse(dom); + try { + // Visit product page + Document doc = Jsoup.connect("https://www.checkers.co.za" + link.getLink()).get(); // Get product info FoodModelM food = new FoodModelM(); + // product name - String productName = productDoc.selectFirst("h1.pdp__name").text(); + Element productNameEl = doc.selectFirst("h1.pdp__name"); + if (productNameEl == null) { + System.out.println("Skipping " + link.getLink() + ", no product name..."); + return; + } + String productName = productNameEl.text(); + if (productName == null || productName.isEmpty()) { + System.out.println("Skipping " + link.getLink() + ", no product name..."); + return; + } System.out.println("Product name: " + productName); food.setName(productName); // product price - String productPrice = productDoc.selectFirst("div.special-price__price").text(); + Element productPriceEl = doc.selectFirst("div.special-price__price"); + if (productPriceEl == null) { + System.out.println("Skipping " + link.getLink() + ", no product price..."); + return; + } + String productPrice = productPriceEl.text(); + if (productPrice == null || productPrice.isEmpty()) { + food.setPrice(-1.0); + } System.out.println("Product price: " + productPrice); food.setPrice(productPrice); // product details - Elements productDetails = productDoc.select("table.pdp__product-information > tbody > tr"); + Elements productDetails = doc.select("table.pdp__product-information > tbody > tr"); String barcode = ""; String quantity = ""; @@ -219,7 +215,12 @@ public void scrape() { for (Element productDetail : productDetails) { if (productDetail.text().toLowerCase().contains("barcode")) { // select second td - barcode = productDetail.selectFirst("td:nth-child(2)").text(); + Element barcodeEl = productDetail.selectFirst("td:nth-child(2)"); + if (barcodeEl == null) { + System.out.println("Skipping " + link.getLink() + ", no barcode..."); + return; + } + barcode = barcodeEl.text(); System.out.println("Barcode: " + barcode); food.setBarcode(barcode); } @@ -227,18 +228,38 @@ public void scrape() { if (productDetail.text().toLowerCase().contains("weight") || productDetail.text().toLowerCase().contains("volume")) { // select second td - quantity = productDetail.selectFirst("td:nth-child(2)").text(); + Element quantityEl = productDetail.selectFirst("td:nth-child(2)"); + if (quantityEl == null) { + System.out.println("Skipping " + link.getLink() + ", no quantity..."); + return; + } + quantity = quantityEl.text(); System.out.println("Quantity: " + quantity); food.setAmount(quantity); } } - // Add food to list - foodModels.add(food); + if (barcode.isEmpty() || food.getBarcode().equals("")) { + System.out.println("Skipping " + link.getLink() + ", no barcode..."); + return; + } - lastRequestTime = System.currentTimeMillis(); - } + // Add food to database + barcodeService.addProduct(food); + + // Add link to visited links + visitedLinkRepository.save(new VisitedLinkModel(link.getLink(), "product", "Checkers")); + + // Remove link from ToVisitLinks + toVisitLinkRepository.deleteById(link.getLink()); - driver.quit(); + System.out.println("Visited " + link.getLink()); + } catch (IOException e) { + System.out.println("Error visiting " + link.getLink() + ", skipping..."); + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + + } } -} \ No newline at end of file +} diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java index 90d9e603..cdb09b0d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java @@ -7,6 +7,8 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.MatchOperation; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; @@ -17,8 +19,11 @@ public class LinkService { @Autowired private MongoTemplate mongoTemplate; - public Optional getNextLink() { + public Optional getNextLink(String store) { + MatchOperation match = Aggregation.match(Criteria.where("store").is(store)); + Aggregation aggregation = Aggregation.newAggregation( + match, Aggregation.sample(1)); AggregationResults results = mongoTemplate.aggregate(aggregation, "ToVisitLinks", @@ -28,4 +33,12 @@ public Optional getNextLink() { return links.isEmpty() ? Optional.empty() : Optional.of(links.get(0)); } + + public Optional getNextCheckersLink() { + return getNextLink("Checkers"); + } + + public Optional getNextWoolworthsLink() { + return getNextLink("Woolworths"); + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java index a2abc472..ed6ef9be 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java @@ -1,11 +1,17 @@ package fellowship.mealmaestro.services.webscraping; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class WebscrapeService { + @Autowired + private LinkService linkService; + + private CheckersScraper checkersScraper; + private volatile boolean isScrapingAllowed = true; @Scheduled(cron = "0 0 6 * * ?") From 3a0741fbb8447ae05da2b3c3181c6ad136b20181 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sun, 10 Sep 2023 17:19:53 +0200 Subject: [PATCH 12/97] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Change=20to=20use=20?= =?UTF-8?q?constructors=20instead=20of=20autowired?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mealmaestro/config/ApplicationConfig.java | 11 ++++ .../controllers/BrowseController.java | 8 +-- .../controllers/MealManagementController.java | 13 +++-- .../controllers/PantryController.java | 8 +-- .../controllers/ProductController.java | 8 +-- .../controllers/SettingsController.java | 8 +-- .../controllers/ShoppingListController.java | 8 +-- .../controllers/UserController.java | 7 ++- .../mongo/DynamicFoodMRepositoryImpl.java | 2 - .../mealmaestro/services/BarcodeService.java | 8 +-- .../mealmaestro/services/BrowseService.java | 8 +-- .../services/MealDatabaseService.java | 16 +++--- .../services/MealManagementService.java | 16 +++--- .../services/OpenaiApiService.java | 13 ++--- .../services/OpenaiPromptBuilder.java | 12 +++-- .../mealmaestro/services/PantryService.java | 24 ++++----- .../services/RecipeBookService.java | 17 +++--- .../mealmaestro/services/SettingsService.java | 17 +++--- .../services/ShoppingListService.java | 29 +++++----- .../mealmaestro/services/UserService.java | 11 ++-- .../services/webscraping/CheckersScraper.java | 53 +++++++++---------- .../services/webscraping/LinkService.java | 8 +-- .../webscraping/WebscrapeService.java | 20 ++----- 23 files changed, 173 insertions(+), 152 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java b/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java index d2abc988..78b64f6e 100644 --- a/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java +++ b/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java @@ -2,6 +2,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; @@ -45,4 +47,13 @@ public PasswordEncoder passwordEncoder() { public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } + + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(4); + scheduler.initialize(); + return scheduler; + } + } diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/BrowseController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/BrowseController.java index 19861125..382e9850 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/BrowseController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/BrowseController.java @@ -2,7 +2,6 @@ import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; @@ -15,8 +14,11 @@ @RestController public class BrowseController { - @Autowired - private BrowseService browseService; + private final BrowseService browseService; + + public BrowseController(BrowseService browseService) { + this.browseService = browseService; + } @GetMapping("/getPopularMeals") public ResponseEntity> getPopularMeals(@RequestHeader("Authorization") String token) { diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java index bdd63bda..6451f0c5 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java @@ -3,7 +3,6 @@ import java.time.LocalDate; import java.util.List; import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -25,10 +24,14 @@ @RestController public class MealManagementController { - @Autowired - private MealManagementService mealManagementService; - @Autowired - private MealDatabaseService mealDatabaseService; + private final MealManagementService mealManagementService; + private final MealDatabaseService mealDatabaseService; + + public MealManagementController(MealManagementService mealManagementService, + MealDatabaseService mealDatabaseService) { + this.mealManagementService = mealManagementService; + this.mealDatabaseService = mealDatabaseService; + } @PostMapping("/getMealPlanForDay") public ResponseEntity> dailyMeals(@Valid @RequestBody DateModel request, diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/PantryController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/PantryController.java index 064c1fbc..5153d937 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/PantryController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/PantryController.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.UUID; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,8 +16,11 @@ @RestController public class PantryController { - @Autowired - private PantryService pantryService; + private final PantryService pantryService; + + public PantryController(PantryService pantryService) { + this.pantryService = pantryService; + } @PostMapping("/addToPantry") public ResponseEntity addToPantry(@Valid @RequestBody FoodModel request, diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java index e590aadf..22251ea4 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java @@ -1,6 +1,5 @@ package fellowship.mealmaestro.controllers; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -14,8 +13,11 @@ @RestController public class ProductController { - @Autowired - private BarcodeService barcodeService; + private final BarcodeService barcodeService; + + public ProductController(BarcodeService barcodeService) { + this.barcodeService = barcodeService; + } @PostMapping("/findProduct") public ResponseEntity findProduct(@RequestBody findBarcodeRequest request, diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/SettingsController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/SettingsController.java index 89b7ab82..30b7f302 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/SettingsController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/SettingsController.java @@ -1,6 +1,5 @@ package fellowship.mealmaestro.controllers; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -14,8 +13,11 @@ @RestController public class SettingsController { - @Autowired - private SettingsService settingsService; + private final SettingsService settingsService; + + public SettingsController(SettingsService settingsService) { + this.settingsService = settingsService; + } @PostMapping("/getSettings") public ResponseEntity getSettings(@RequestHeader("Authorization") String token) { diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/ShoppingListController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/ShoppingListController.java index ff444866..a41cffa5 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/ShoppingListController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/ShoppingListController.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.UUID; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,8 +16,11 @@ @RestController public class ShoppingListController { - @Autowired - private ShoppingListService shoppingListService; + private final ShoppingListService shoppingListService; + + public ShoppingListController(ShoppingListService shoppingListService) { + this.shoppingListService = shoppingListService; + } @PostMapping("/addToShoppingList") public ResponseEntity addToShoppingList(@Valid @RequestBody FoodModel request, diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java index d14b3b60..ed17d8a7 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java @@ -2,7 +2,6 @@ import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -22,13 +21,13 @@ @RestController public class UserController { - @Autowired - private UserService userService; + private final UserService userService; private final AuthenticationService authenticationService; - public UserController(AuthenticationService authenticationService) { + public UserController(AuthenticationService authenticationService, UserService userService) { this.authenticationService = authenticationService; + this.userService = userService; } @PostMapping("/findByEmail") diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java index 720216a3..67c57f8d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/mongo/DynamicFoodMRepositoryImpl.java @@ -2,7 +2,6 @@ import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import fellowship.mealmaestro.models.mongo.FoodModelM; @@ -10,7 +9,6 @@ public class DynamicFoodMRepositoryImpl implements DynamicFoodMRepository { private final MongoTemplate mongoTemplate; - @Autowired public DynamicFoodMRepositoryImpl(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java b/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java index e36c10c3..ea2e86ad 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/BarcodeService.java @@ -2,7 +2,6 @@ import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.mongo.FoodModelM; @@ -12,8 +11,11 @@ @Service public class BarcodeService { - @Autowired - private FoodMRepository foodMRepository; + private final FoodMRepository foodMRepository; + + public BarcodeService(FoodMRepository foodMRepository) { + this.foodMRepository = foodMRepository; + } public FoodModelM findProduct(findBarcodeRequest request) { System.out.println(request.getStore()); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/BrowseService.java b/backend/src/main/java/fellowship/mealmaestro/services/BrowseService.java index 969c187c..7b5d1c5f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/BrowseService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/BrowseService.java @@ -2,7 +2,6 @@ import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.neo4j.MealModel; @@ -11,8 +10,11 @@ @Service public class BrowseService { - @Autowired - private MealRepository mealRepository; + private final MealRepository mealRepository; + + public BrowseService(MealRepository mealRepository) { + this.mealRepository = mealRepository; + } public List getPopularMeals() { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java index 524784f7..1e1460f9 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,14 +21,17 @@ @Service public class MealDatabaseService { - @Autowired - private JwtService jwtService; + private final JwtService jwtService; - @Autowired - private MealRepository mealRepository; + private final MealRepository mealRepository; - @Autowired - private UserRepository userRepository; + private final UserRepository userRepository; + + public MealDatabaseService(JwtService jwtService, MealRepository mealRepository, UserRepository userRepository) { + this.jwtService = jwtService; + this.mealRepository = mealRepository; + this.userRepository = userRepository; + } @Transactional public List saveMeals(List mealsToSave, LocalDate date, String token) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java index 4c51817e..2ce340f5 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java @@ -3,7 +3,6 @@ import java.io.File; import java.io.IOException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; @@ -20,13 +19,16 @@ @Service public class MealManagementService { - @Autowired - private OpenaiApiService openaiApiService; - @Autowired - private ObjectMapper objectMapper; + private final OpenaiApiService openaiApiService; + private final ObjectMapper objectMapper; + private final UnsplashService unsplashService; - @Autowired - private UnsplashService unsplashService; + public MealManagementService(OpenaiApiService openaiApiService, ObjectMapper objectMapper, + UnsplashService unsplashService) { + this.openaiApiService = openaiApiService; + this.objectMapper = objectMapper; + this.unsplashService = unsplashService; + } public MealModel generateMeal(String mealType, String token) { MealModel defaultMeal = new MealModel("Bread", "1. Toast the bread", "Delicious Bread", diff --git a/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java b/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java index 66a019f9..300d4662 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java @@ -3,7 +3,6 @@ import java.time.Duration; import java.util.concurrent.TimeoutException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; @@ -21,13 +20,16 @@ @Service public class OpenaiApiService { private static final String OPENAI_URL = "https://api.openai.com/v1/chat/completions"; - private final static String API_KEY; private final WebClient webClient; + private final ObjectMapper jsonMapper; + private final OpenaiPromptBuilder pBuilder; - public OpenaiApiService(WebClient.Builder webClientBuilder) { + public OpenaiApiService(WebClient.Builder webClientBuilder, ObjectMapper jsonMapper, OpenaiPromptBuilder pBuilder) { this.webClient = webClientBuilder.build(); + this.jsonMapper = jsonMapper; + this.pBuilder = pBuilder; } static { @@ -49,11 +51,6 @@ public OpenaiApiService(WebClient.Builder webClientBuilder) { API_KEY = apiKey; } - @Autowired - private ObjectMapper jsonMapper = new ObjectMapper(); - @Autowired - private OpenaiPromptBuilder pBuilder = new OpenaiPromptBuilder(); - public String fetchMealResponse(String type, String token) throws JsonMappingException, JsonProcessingException { String jsonResponse = getJSONResponse(type, token); if (jsonResponse.equals("Timeout")) { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/OpenaiPromptBuilder.java b/backend/src/main/java/fellowship/mealmaestro/services/OpenaiPromptBuilder.java index e3673f0c..f8c5f0a8 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/OpenaiPromptBuilder.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/OpenaiPromptBuilder.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Random; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; @@ -18,14 +17,17 @@ @Service public class OpenaiPromptBuilder { - @Autowired - private JwtService jwtService; + private final JwtService jwtService; - @Autowired - private UserRepository userRepository; + private final UserRepository userRepository; private Random rand; + public OpenaiPromptBuilder(JwtService jwtService, UserRepository userRepository) { + this.jwtService = jwtService; + this.userRepository = userRepository; + } + @PostConstruct public void init() { rand = new Random(System.currentTimeMillis()); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/PantryService.java b/backend/src/main/java/fellowship/mealmaestro/services/PantryService.java index 441f41e5..d5b2715b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/PantryService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/PantryService.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,17 +17,18 @@ @Service public class PantryService { - @Autowired - private JwtService jwtService; - - @Autowired - private PantryRepository pantryRepository; - - @Autowired - private UserRepository userRepository; - - @Autowired - private FoodRepository foodRepository; + private final JwtService jwtService; + private final PantryRepository pantryRepository; + private final UserRepository userRepository; + private final FoodRepository foodRepository; + + public PantryService(JwtService jwtService, PantryRepository pantryRepository, UserRepository userRepository, + FoodRepository foodRepository) { + this.jwtService = jwtService; + this.pantryRepository = pantryRepository; + this.userRepository = userRepository; + this.foodRepository = foodRepository; + } @Transactional public FoodModel addToPantry(FoodModel food, String token) { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecipeBookService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecipeBookService.java index beab57a5..4deaa018 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/RecipeBookService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/RecipeBookService.java @@ -1,6 +1,5 @@ package fellowship.mealmaestro.services; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @@ -15,14 +14,16 @@ @Service public class RecipeBookService { - @Autowired - private JwtService jwtService; + private final JwtService jwtService; + private final RecipeBookRepository recipeBookRepository; + private final UserRepository userRepository; - @Autowired - private RecipeBookRepository recipeBookRepository; - - @Autowired - private UserRepository userRepository; + public RecipeBookService(JwtService jwtService, RecipeBookRepository recipeBookRepository, + UserRepository userRepository) { + this.jwtService = jwtService; + this.recipeBookRepository = recipeBookRepository; + this.userRepository = userRepository; + } public MealModel addRecipe(MealModel recipe, String token) { String email = jwtService.extractUserEmail(token); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/SettingsService.java b/backend/src/main/java/fellowship/mealmaestro/services/SettingsService.java index 6a8ad56a..4cbf0ae7 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/SettingsService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/SettingsService.java @@ -1,6 +1,5 @@ package fellowship.mealmaestro.services; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.neo4j.SettingsModel; @@ -12,14 +11,16 @@ @Service public class SettingsService { - @Autowired - private JwtService jwtService; + private final JwtService jwtService; + private final SettingsRepository SettingsRepository; + private final UserRepository userRepository; - @Autowired - private SettingsRepository SettingsRepository; - - @Autowired - private UserRepository userRepository; + public SettingsService(JwtService jwtService, SettingsRepository SettingsRepository, + UserRepository userRepository) { + this.jwtService = jwtService; + this.SettingsRepository = SettingsRepository; + this.userRepository = userRepository; + } public SettingsModel getSettings(String token) { String email = jwtService.extractUserEmail(token); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/ShoppingListService.java b/backend/src/main/java/fellowship/mealmaestro/services/ShoppingListService.java index c7c727c7..bf1026a6 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/ShoppingListService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/ShoppingListService.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,20 +19,20 @@ @Service public class ShoppingListService { - @Autowired - private JwtService jwtService; - - @Autowired - private ShoppingListRepository shoppingListRepository; - - @Autowired - private UserRepository userRepository; - - @Autowired - private PantryRepository pantryRepository; - - @Autowired - private FoodRepository foodRepository; + private final JwtService jwtService; + private final ShoppingListRepository shoppingListRepository; + private final UserRepository userRepository; + private final PantryRepository pantryRepository; + private final FoodRepository foodRepository; + + public ShoppingListService(JwtService jwtService, ShoppingListRepository shoppingListRepository, + UserRepository userRepository, PantryRepository pantryRepository, FoodRepository foodRepository) { + this.jwtService = jwtService; + this.shoppingListRepository = shoppingListRepository; + this.userRepository = userRepository; + this.pantryRepository = pantryRepository; + this.foodRepository = foodRepository; + } @Transactional public FoodModel addToShoppingList(FoodModel food, String token) { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/UserService.java b/backend/src/main/java/fellowship/mealmaestro/services/UserService.java index b3cf3333..b2f14868 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/UserService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/UserService.java @@ -2,7 +2,6 @@ import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.UpdateUserRequestModel; @@ -13,11 +12,13 @@ @Service public class UserService { - @Autowired - private UserRepository userRepository; + private final UserRepository userRepository; + private final JwtService jwtService; - @Autowired - private JwtService jwtService; + public UserService(UserRepository userRepository, JwtService jwtService) { + this.userRepository = userRepository; + this.jwtService = jwtService; + } public Optional findByEmail(String email) { return userRepository.findByEmail(email); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 1720fd00..11b41072 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -1,7 +1,5 @@ package fellowship.mealmaestro.services.webscraping; -import org.springframework.beans.factory.annotation.Autowired; - import fellowship.mealmaestro.models.mongo.FoodModelM; import fellowship.mealmaestro.models.mongo.ToVisitLinkModel; import fellowship.mealmaestro.models.mongo.VisitedLinkModel; @@ -19,23 +17,24 @@ import org.jsoup.select.Elements; public class CheckersScraper { - @Autowired - private ToVisitLinkRepository toVisitLinkRepository; + private final ToVisitLinkRepository toVisitLinkRepository; - @Autowired - private VisitedLinkRepository visitedLinkRepository; + private final VisitedLinkRepository visitedLinkRepository; - @Autowired - private LinkService linkService; + private final LinkService linkService; - @Autowired - private BarcodeService barcodeService; + private final BarcodeService barcodeService; - private long lastRequestTime; + public CheckersScraper(ToVisitLinkRepository toVisitLinkRepository, VisitedLinkRepository visitedLinkRepository, + LinkService linkService, BarcodeService barcodeService) { + this.toVisitLinkRepository = toVisitLinkRepository; + this.visitedLinkRepository = visitedLinkRepository; + this.linkService = linkService; + this.barcodeService = barcodeService; + } public void getLocLinks() { // Visit categories sitemap to get all locs - lastRequestTime = 0; Optional visited = visitedLinkRepository .findById("https://www.checkers.co.za/sitemap/medias/Category-checkersZA-0.xml"); @@ -71,22 +70,7 @@ public void getLocLinks() { public ToVisitLinkModel getNextLink() { // Get next link to visit - - long currentTime = System.currentTimeMillis(); - long timeSinceLastRequest = currentTime - lastRequestTime; - - // Wait 10 seconds between requests - if (timeSinceLastRequest < 10000) { - try { - System.out.println("Waiting " + (10000 - timeSinceLastRequest) + "ms"); - Thread.sleep(10000 - timeSinceLastRequest); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - Optional toVisitLink = linkService.getNextCheckersLink(); - lastRequestTime = System.currentTimeMillis(); if (toVisitLink.isPresent()) { return toVisitLink.get(); @@ -111,6 +95,7 @@ public void handleCategoryLink(ToVisitLinkModel link) { // if link has been visited and it has been less than 1 month since last visit, // skip if (visited.isPresent() && visited.get().getLastVisited().plusMonths(1).isAfter(LocalDate.now())) { + toVisitLinkRepository.deleteById(link.getLink()); System.out.println("Skipping " + link.getLink() + ", already visited..."); return; } @@ -168,6 +153,7 @@ public void handleProductLink(ToVisitLinkModel link) { Optional visited = visitedLinkRepository.findById(link.getLink()); if (visited.isPresent() && visited.get().getLastVisited().plusMonths(1).isAfter(LocalDate.now())) { + toVisitLinkRepository.deleteById(link.getLink()); System.out.println("Skipping " + link.getLink() + ", already visited..."); return; } @@ -262,4 +248,17 @@ public void handleProductLink(ToVisitLinkModel link) { } } + + public void scrape() { + // Get next link to visit + ToVisitLinkModel toVisitLink = getNextLink(); + + if (toVisitLink == null) { + System.out.println("No links to visit..."); + return; + } + + // Handle link + handleLink(toVisitLink); + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java index cdb09b0d..8e838fa5 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/LinkService.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; @@ -16,8 +15,11 @@ @Service public class LinkService { - @Autowired - private MongoTemplate mongoTemplate; + private final MongoTemplate mongoTemplate; + + public LinkService(MongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } public Optional getNextLink(String store) { MatchOperation match = Aggregation.match(Criteria.where("store").is(store)); diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java index ed6ef9be..4e35f3f7 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java @@ -1,26 +1,16 @@ package fellowship.mealmaestro.services.webscraping; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class WebscrapeService { - @Autowired - private LinkService linkService; + private final LinkService linkService; - private CheckersScraper checkersScraper; + private final CheckersScraper checkersScraper; - private volatile boolean isScrapingAllowed = true; - - @Scheduled(cron = "0 0 6 * * ?") - public void startScrape() { - System.out.println("Scraping started..."); - } - - @Scheduled(cron = "0 42 10 * * ?") - public void stopScraping() { - System.out.println("Scraping stopped..."); + public WebscrapeService(LinkService linkService, CheckersScraper checkersScraper) { + this.linkService = linkService; + this.checkersScraper = checkersScraper; } } From 9418dfd6ebf18d52e58722cd5a3ef0bc0de225d1 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:40:56 +0200 Subject: [PATCH 13/97] =?UTF-8?q?=F0=9F=9A=A7=20Scraping=20scheduling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mealmaestro/config/ApplicationConfig.java | 2 +- .../services/webscraping/CheckersScraper.java | 1 + .../webscraping/WebscrapeService.java | 70 +++++++++++++++++-- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java b/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java index 78b64f6e..db57ef1f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java +++ b/backend/src/main/java/fellowship/mealmaestro/config/ApplicationConfig.java @@ -51,7 +51,7 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setPoolSize(4); + scheduler.setPoolSize(6); scheduler.initialize(); return scheduler; } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 11b41072..99203e62 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -259,6 +259,7 @@ public void scrape() { } // Handle link + System.out.println("###" + System.currentTimeMillis() + "###"); handleLink(toVisitLink); } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java index 4e35f3f7..9fddce11 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java @@ -1,16 +1,78 @@ package fellowship.mealmaestro.services.webscraping; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.concurrent.ScheduledFuture; + +import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Service; +import jakarta.annotation.PostConstruct; + @Service public class WebscrapeService { - private final LinkService linkService; - private final CheckersScraper checkersScraper; + private final TaskScheduler taskScheduler; - public WebscrapeService(LinkService linkService, CheckersScraper checkersScraper) { - this.linkService = linkService; + private ScheduledFuture> checkersScrapingTask; + + public WebscrapeService(CheckersScraper checkersScraper, TaskScheduler taskScheduler) { this.checkersScraper = checkersScraper; + this.taskScheduler = taskScheduler; + } + + @PostConstruct + public void init() { + startScraping(); + } + + private LocalDateTime getStartTime() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime next6AM = now.withHour(6).withMinute(0).withSecond(0); + + if (now.isAfter(next6AM) || now.isEqual(next6AM)) { + return next6AM.plusDays(1); + } else { + return next6AM; + } + } + + private LocalDateTime getStopTime() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime next1040AM = now.withHour(10).withMinute(40).withSecond(0); + + if (now.isAfter(next1040AM) || now.isEqual(next1040AM)) { + return next1040AM.plusDays(1); + } else { + return next1040AM; + } + } + + private void startScraping() { + LocalDateTime startTime = getStartTime(); + + // schedule task to start at 6am and use a 10s fixedDelay + Duration tenSeconds = Duration.ofSeconds(10); + checkersScrapingTask = taskScheduler.scheduleWithFixedDelay(() -> { + checkersScraper.scrape(); + }, startTime.toInstant(ZoneOffset.ofHours(2)), tenSeconds); + + // schedule task to stop at 10:40am + LocalDateTime stopTime = getStopTime(); + taskScheduler.schedule(() -> { + stopScraping(); + }, stopTime.toInstant(ZoneOffset.ofHours(2))); } + + private void stopScraping() { + if (checkersScrapingTask != null) { + checkersScrapingTask.cancel(false); + } + + // schedule tasks for next day + startScraping(); + } + } From 74fc509b8583c97c1289331288215c0cbaedd6b4 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:06:39 +0200 Subject: [PATCH 14/97] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Added=20index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java index a731db2f..e547f8a7 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/ToVisitLinkModel.java @@ -1,6 +1,7 @@ package fellowship.mealmaestro.models.mongo; import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import lombok.Getter; @@ -15,6 +16,7 @@ public class ToVisitLinkModel { private String type; + @Indexed private String store; public ToVisitLinkModel(String link, String type, String store) { From 8512585d1fdbe072467e83573e98549c7f118caf Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Mon, 11 Sep 2023 13:27:09 +0200 Subject: [PATCH 15/97] =?UTF-8?q?=F0=9F=90=9B=20small=20fixes=20and=20exte?= =?UTF-8?q?nded=20timeout=20duration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fellowship/mealmaestro/services/MealManagementService.java | 1 + .../java/fellowship/mealmaestro/services/OpenaiApiService.java | 2 +- frontend/src/app/pages/profile/profile.page.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java index b240700b..4c51817e 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java @@ -32,6 +32,7 @@ public MealModel generateMeal(String mealType, String token) { MealModel defaultMeal = new MealModel("Bread", "1. Toast the bread", "Delicious Bread", "https://images.unsplash.com/photo-1598373182133-52452f7691ef?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80", "Bread", "5 minutes"); + defaultMeal.setType("breakfast"); try { JsonNode mealJson = objectMapper.readTree(openaiApiService.fetchMealResponse(mealType, token)); int i = 0; diff --git a/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java b/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java index 66a019f9..c25e558b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/OpenaiApiService.java @@ -107,7 +107,7 @@ public String getJSONResponse(String Type, String token) throws JsonProcessingEx .body(Mono.just(jsonRequest), String.class) .retrieve() .bodyToMono(String.class) - .timeout(Duration.ofSeconds(10)) + .timeout(Duration.ofSeconds(30)) .block(); return response; diff --git a/frontend/src/app/pages/profile/profile.page.ts b/frontend/src/app/pages/profile/profile.page.ts index e4dd6a4c..2a4a4537 100644 --- a/frontend/src/app/pages/profile/profile.page.ts +++ b/frontend/src/app/pages/profile/profile.page.ts @@ -166,7 +166,7 @@ export class ProfilePage implements OnInit, ViewWillEnter { } }, }); - this.loginService.setSettingsRefreshed(false); + this.loginService.setSettingsRefreshed(true); } } From 90d8a6be47c8da4e51614d67f6ef34fa2aedfdf6 Mon Sep 17 00:00:00 2001 From: SkulderLock <78735770+SkulderLock@users.noreply.github.com> Date: Tue, 12 Sep 2023 15:54:24 +0200 Subject: [PATCH 16/97] =?UTF-8?q?=E2=9C=A8=20Checkers=20Scraper=20+=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 + backend/build.gradle | 1 + .../controllers/ProductController.java | 12 +- .../mealmaestro/models/mongo/FoodModelM.java | 8 +- .../services/webscraping/CheckersScraper.java | 106 ++++++++++++++---- .../webscraping/WebscrapeService.java | 12 +- .../src/main/resources/application.properties | 2 + 7 files changed, 120 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index c96a4da0..caec0976 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,12 @@ $RECYCLE.BIN/ *.log log.txt +mealmaestro-logs.txt + + +# ios +ios/ +android/ .env diff --git a/backend/build.gradle b/backend/build.gradle index a3d59903..2cf073f5 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-neo4j' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-logging' implementation 'org.seleniumhq.selenium:selenium-java:4.12.1' implementation 'org.jsoup:jsoup:1.16.1' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java index 22251ea4..3bcffa10 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/ProductController.java @@ -1,6 +1,7 @@ package fellowship.mealmaestro.controllers; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -9,14 +10,17 @@ import fellowship.mealmaestro.models.mongo.findBarcodeRequest; import fellowship.mealmaestro.models.mongo.FoodModelM; import fellowship.mealmaestro.services.BarcodeService; +import fellowship.mealmaestro.services.webscraping.CheckersScraper; @RestController public class ProductController { private final BarcodeService barcodeService; + private final CheckersScraper checkersScraper; - public ProductController(BarcodeService barcodeService) { + public ProductController(BarcodeService barcodeService, CheckersScraper checkersScraper) { this.barcodeService = barcodeService; + this.checkersScraper = checkersScraper; } @PostMapping("/findProduct") @@ -36,4 +40,10 @@ public ResponseEntity addProduct(@RequestBody FoodModelM product, } return ResponseEntity.ok(barcodeService.addProduct(product)); } + + @GetMapping("/loc") + public ResponseEntity loc() { + checkersScraper.getLocLinks(); + return ResponseEntity.ok("loc"); + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java index ed08d77d..a8b3d84c 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/mongo/FoodModelM.java @@ -47,8 +47,12 @@ public String toString() { public void setPrice(String price) { // remove R sign - price = price.substring(1); - this.price = Double.parseDouble(price); + Pattern pattern = Pattern.compile("(R)([0-9.]+)"); + Matcher matcher = pattern.matcher(price); + + if (matcher.find()) { + this.price = Double.parseDouble(matcher.group(2)); + } } public void setPrice(Double price) { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java index 99203e62..3c6ab80b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/CheckersScraper.java @@ -15,7 +15,11 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +@Service public class CheckersScraper { private final ToVisitLinkRepository toVisitLinkRepository; @@ -25,6 +29,8 @@ public class CheckersScraper { private final BarcodeService barcodeService; + private static final Logger logger = LoggerFactory.getLogger(CheckersScraper.class); + public CheckersScraper(ToVisitLinkRepository toVisitLinkRepository, VisitedLinkRepository visitedLinkRepository, LinkService linkService, BarcodeService barcodeService) { this.toVisitLinkRepository = toVisitLinkRepository; @@ -90,22 +96,53 @@ public void handleLink(ToVisitLinkModel toVisitLink) { public void handleCategoryLink(ToVisitLinkModel link) { // Visit category page and get all product links and pagination links + logger.info("Handling Category Link: " + link.getLink()); Optional visited = visitedLinkRepository.findById(link.getLink()); // if link has been visited and it has been less than 1 month since last visit, // skip if (visited.isPresent() && visited.get().getLastVisited().plusMonths(1).isAfter(LocalDate.now())) { toVisitLinkRepository.deleteById(link.getLink()); - System.out.println("Skipping " + link.getLink() + ", already visited..."); + logger.info("Skipping " + link.getLink() + ", already visited..."); return; } try { - Document doc = Jsoup.connect("https://www.checkers.co.za" + link.getLink()).get(); + // if link starts with /, add domain + Document doc; + if (link.getLink().startsWith("/")) { + doc = Jsoup.connect("http://www.checkers.co.za" + link.getLink()).userAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76") + .header("Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") + .header("Accept-Encoding", "gzip, deflate, br") + .header("Accept-Language", "en-US,en;q=0.9") + .get(); + } else { + doc = Jsoup.connect(link.getLink()).userAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76") + .header("Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") + .header("Accept-Encoding", "gzip, deflate, br") + .header("Accept-Language", "en-US,en;q=0.9") + .get(); + } + logger.info("Connecting to " + doc.title() + "..."); + if (doc.title().contains("Captcha")) { + logger.info("######################################"); + logger.info("Error encountered captcha, skipping..."); + logger.info("######################################"); + return; + } // Get product links Elements productPageLinks = doc.select("h3.item-product__name > a"); + if (productPageLinks == null) { + logger.info("Skipping " + link.getLink() + ", no product links..."); + return; + } + for (int i = 0; i < productPageLinks.size(); i++) { String productPageLink = productPageLinks.get(i).attr("href"); @@ -132,13 +169,14 @@ public void handleCategoryLink(ToVisitLinkModel link) { // Add link to visited links visitedLinkRepository.save(new VisitedLinkModel(link.getLink(), "category", "Checkers")); + logger.info("Saving " + link.getLink() + " to visited links..."); // Remove link from ToVisitLinks toVisitLinkRepository.deleteById(link.getLink()); - System.out.println("Visited " + link.getLink()); + logger.info("Visited " + link.getLink()); } catch (IOException e) { - System.out.println("Error visiting " + link.getLink() + ", skipping..."); + logger.info("Error visiting " + link.getLink() + ", skipping..."); e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); @@ -150,30 +188,49 @@ public void handleProductLink(ToVisitLinkModel link) { // Visit product page and get product info // Check if link has been visited + logger.info("Handling Product Link: " + link.getLink()); Optional visited = visitedLinkRepository.findById(link.getLink()); if (visited.isPresent() && visited.get().getLastVisited().plusMonths(1).isAfter(LocalDate.now())) { toVisitLinkRepository.deleteById(link.getLink()); - System.out.println("Skipping " + link.getLink() + ", already visited..."); + logger.info("Skipping " + link.getLink() + ", already visited..."); return; } try { // Visit product page - Document doc = Jsoup.connect("https://www.checkers.co.za" + link.getLink()).get(); - + // if link starts with /, add domain + Document doc; + if (link.getLink().startsWith("/")) { + doc = Jsoup.connect("http://www.checkers.co.za" + link.getLink()).userAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36") + .header("Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") + .header("Accept-Encoding", "gzip, deflate, br") + .header("Accept-Language", "en-US,en;q=0.9") + .get(); + } else { + doc = Jsoup.connect(link.getLink()).userAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36") + .header("Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") + .header("Accept-Encoding", "gzip, deflate, br") + .header("Accept-Language", "en-US,en;q=0.9") + .get(); + } + logger.info("Connecting to " + doc.title() + "..."); // Get product info FoodModelM food = new FoodModelM(); // product name Element productNameEl = doc.selectFirst("h1.pdp__name"); if (productNameEl == null) { - System.out.println("Skipping " + link.getLink() + ", no product name..."); + logger.info("Skipping " + link.getLink() + ", no product name..."); return; } String productName = productNameEl.text(); if (productName == null || productName.isEmpty()) { - System.out.println("Skipping " + link.getLink() + ", no product name..."); + logger.info("Skipping " + link.getLink() + ", no product name..."); return; } System.out.println("Product name: " + productName); @@ -182,15 +239,17 @@ public void handleProductLink(ToVisitLinkModel link) { // product price Element productPriceEl = doc.selectFirst("div.special-price__price"); if (productPriceEl == null) { - System.out.println("Skipping " + link.getLink() + ", no product price..."); - return; - } - String productPrice = productPriceEl.text(); - if (productPrice == null || productPrice.isEmpty()) { + logger.info("No product price"); food.setPrice(-1.0); + } else { + + String productPrice = productPriceEl.text(); + if (productPrice == null || productPrice.isEmpty()) { + food.setPrice(-1.0); + } + System.out.println("Product price: " + productPrice); + food.setPrice(productPrice); } - System.out.println("Product price: " + productPrice); - food.setPrice(productPrice); // product details Elements productDetails = doc.select("table.pdp__product-information > tbody > tr"); @@ -203,7 +262,7 @@ public void handleProductLink(ToVisitLinkModel link) { // select second td Element barcodeEl = productDetail.selectFirst("td:nth-child(2)"); if (barcodeEl == null) { - System.out.println("Skipping " + link.getLink() + ", no barcode..."); + logger.info("Skipping " + link.getLink() + ", no barcode..."); return; } barcode = barcodeEl.text(); @@ -216,7 +275,7 @@ public void handleProductLink(ToVisitLinkModel link) { // select second td Element quantityEl = productDetail.selectFirst("td:nth-child(2)"); if (quantityEl == null) { - System.out.println("Skipping " + link.getLink() + ", no quantity..."); + logger.info("Skipping " + link.getLink() + ", no quantity..."); return; } quantity = quantityEl.text(); @@ -226,22 +285,25 @@ public void handleProductLink(ToVisitLinkModel link) { } if (barcode.isEmpty() || food.getBarcode().equals("")) { - System.out.println("Skipping " + link.getLink() + ", no barcode..."); + logger.info("Skipping " + link.getLink() + ", no barcode..."); return; } // Add food to database + food.setStore("Checkers"); barcodeService.addProduct(food); + logger.info("Saving " + food.getName() + " to database..."); // Add link to visited links visitedLinkRepository.save(new VisitedLinkModel(link.getLink(), "product", "Checkers")); + logger.info("Saving " + link.getLink() + " to visited links..."); // Remove link from ToVisitLinks toVisitLinkRepository.deleteById(link.getLink()); - System.out.println("Visited " + link.getLink()); + logger.info("Visited " + link.getLink()); } catch (IOException e) { - System.out.println("Error visiting " + link.getLink() + ", skipping..."); + logger.info("Error visiting " + link.getLink() + ", skipping..."); e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); @@ -259,7 +321,7 @@ public void scrape() { } // Handle link - System.out.println("###" + System.currentTimeMillis() + "###"); + logger.info("### " + System.currentTimeMillis() + " ###"); handleLink(toVisitLink); } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java index 9fddce11..858f93b2 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/webscraping/WebscrapeService.java @@ -5,6 +5,8 @@ import java.time.ZoneOffset; import java.util.concurrent.ScheduledFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Service; @@ -16,6 +18,8 @@ public class WebscrapeService { private final CheckersScraper checkersScraper; private final TaskScheduler taskScheduler; + private static final Logger logger = LoggerFactory.getLogger(WebscrapeService.class); + private ScheduledFuture> checkersScrapingTask; public WebscrapeService(CheckersScraper checkersScraper, TaskScheduler taskScheduler) { @@ -25,12 +29,13 @@ public WebscrapeService(CheckersScraper checkersScraper, TaskScheduler taskSched @PostConstruct public void init() { + System.out.println("WebscrapeService init"); startScraping(); } private LocalDateTime getStartTime() { LocalDateTime now = LocalDateTime.now(); - LocalDateTime next6AM = now.withHour(6).withMinute(0).withSecond(0); + LocalDateTime next6AM = now.withHour(13).withMinute(55).withSecond(0); if (now.isAfter(next6AM) || now.isEqual(next6AM)) { return next6AM.plusDays(1); @@ -54,22 +59,25 @@ private void startScraping() { LocalDateTime startTime = getStartTime(); // schedule task to start at 6am and use a 10s fixedDelay - Duration tenSeconds = Duration.ofSeconds(10); + Duration tenSeconds = Duration.ofSeconds(24); checkersScrapingTask = taskScheduler.scheduleWithFixedDelay(() -> { checkersScraper.scrape(); }, startTime.toInstant(ZoneOffset.ofHours(2)), tenSeconds); + logger.info("Scheduled scraping task to start at {}", startTime); // schedule task to stop at 10:40am LocalDateTime stopTime = getStopTime(); taskScheduler.schedule(() -> { stopScraping(); }, stopTime.toInstant(ZoneOffset.ofHours(2))); + logger.info("Scheduled scraping task to stop at {}", stopTime); } private void stopScraping() { if (checkersScrapingTask != null) { checkersScrapingTask.cancel(false); } + logger.info("Stopped scraping task"); // schedule tasks for next day startScraping(); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index e69de29b..7ccfd96f 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.root=INFO +logging.file.name=logs/mealmaestro-logs.txt \ No newline at end of file From 7c22dba691aede69dfeedce01669264ce6468ba3 Mon Sep 17 00:00:00 2001 From: Skulderlock <78735770+SkulderLock@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:01:31 +0200 Subject: [PATCH 17/97] =?UTF-8?q?=E2=9C=A8=20Price=20shown=20in=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/models/fooditem.model.ts | 2 +- .../src/app/pages/pantry/pantry.page.html | 7 ++ frontend/src/app/pages/pantry/pantry.page.ts | 67 ++++++++++--------- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/models/fooditem.model.ts b/frontend/src/app/models/fooditem.model.ts index 2afc89fa..14c865a2 100644 --- a/frontend/src/app/models/fooditem.model.ts +++ b/frontend/src/app/models/fooditem.model.ts @@ -1,7 +1,7 @@ export interface FoodItemI { name: string; quantity: number | null; - unit: 'kg' | 'g' | 'l' | 'ml' | 'pcs'; + unit: 'kg' | 'g' | 'l' | 'ml' | 'pcs' | undefined; id?: number; price?: number; } diff --git a/frontend/src/app/pages/pantry/pantry.page.html b/frontend/src/app/pages/pantry/pantry.page.html index a7d5cdde..b35dbf4b 100644 --- a/frontend/src/app/pages/pantry/pantry.page.html +++ b/frontend/src/app/pages/pantry/pantry.page.html @@ -262,6 +262,13 @@ + + + + Price: R{{ newItem.price }} + + + diff --git a/frontend/src/app/pages/pantry/pantry.page.ts b/frontend/src/app/pages/pantry/pantry.page.ts index 97b58055..7cfc0e2c 100644 --- a/frontend/src/app/pages/pantry/pantry.page.ts +++ b/frontend/src/app/pages/pantry/pantry.page.ts @@ -44,7 +44,7 @@ export class PantryPage implements OnInit, ViewWillEnter { foodListItem!: QueryList; @ViewChild(IonModal) modal!: IonModal; - isBarcodeSupported: boolean = true; + isBarcodeSupported: boolean = false; segment: 'pantry' | 'shopping' | null = 'pantry'; isLoading: boolean = false; pantryItems: FoodItemI[] = []; @@ -54,7 +54,8 @@ export class PantryPage implements OnInit, ViewWillEnter { newItem: FoodItemI = { name: '', quantity: null, - unit: 'pcs', + unit: undefined, + price: undefined, }; constructor( @@ -69,9 +70,9 @@ export class PantryPage implements OnInit, ViewWillEnter { ) {} async ngOnInit() { - // BarcodeScanner.isSupported().then((result) => { - // this.isBarcodeSupported = result.supported; - // }); + BarcodeScanner.isSupported().then((result) => { + this.isBarcodeSupported = result.supported; + }); } async ionViewWillEnter() { @@ -152,6 +153,7 @@ export class PantryPage implements OnInit, ViewWillEnter { name: '', quantity: null, unit: 'pcs', + price: undefined, }; } } @@ -186,6 +188,7 @@ export class PantryPage implements OnInit, ViewWillEnter { name: '', quantity: null, unit: 'pcs', + price: undefined, }; } } @@ -313,6 +316,7 @@ export class PantryPage implements OnInit, ViewWillEnter { name: '', quantity: null, unit: 'pcs', + price: undefined, }; } @@ -460,32 +464,32 @@ export class PantryPage implements OnInit, ViewWillEnter { } async scan(): Promise { - // const granted = await this.requestPermissions(); - // if (!granted) { - // this.errorHandlerService.presentErrorToast( - // 'Please grant camera permissions to use this feature', - // 'Camera permissions not granted' - // ); - // return; - // } - - // const result = await BarcodeScanner.scan(); - - // if ( - // result.barcodes.length === 0 || - // result.barcodes[0].displayValue === '' || - // result.barcodes[0].displayValue === null || - // result.barcodes[0].displayValue === undefined - // ) { - // return; - // } - let result = { - barcodes: [ - { - displayValue: '13761238123', // for testing - }, - ], - }; + const granted = await this.requestPermissions(); + if (!granted) { + this.errorHandlerService.presentErrorToast( + 'Please grant camera permissions to use this feature', + 'Camera permissions not granted' + ); + return; + } + + const result = await BarcodeScanner.scan(); + + if ( + result.barcodes.length === 0 || + result.barcodes[0].displayValue === '' || + result.barcodes[0].displayValue === null || + result.barcodes[0].displayValue === undefined + ) { + return; + } + // let result = { + // barcodes: [ + // { + // displayValue: '13761238123', // for testing + // }, + // ], + // }; if (this.loginService.isShoppingAt() === '') { this.askShoppingLocation(result); @@ -565,6 +569,7 @@ export class PantryPage implements OnInit, ViewWillEnter { name: data.name, quantity: null, unit: 'pcs', + price: undefined, }; this.modal.present(); }, From 13177e2622fbd92627b319ec5bfa019e46073837 Mon Sep 17 00:00:00 2001 From: Skulderlock <78735770+SkulderLock@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:39:14 +0200 Subject: [PATCH 18/97] =?UTF-8?q?=E2=9C=A8=20Total=20Price=20showing=20on?= =?UTF-8?q?=20shopping=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/pages/pantry/pantry.page.html | 3 +++ frontend/src/app/pages/pantry/pantry.page.scss | 10 ++++++++++ frontend/src/app/pages/pantry/pantry.page.ts | 14 ++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/frontend/src/app/pages/pantry/pantry.page.html b/frontend/src/app/pages/pantry/pantry.page.html index b35dbf4b..ed842486 100644 --- a/frontend/src/app/pages/pantry/pantry.page.html +++ b/frontend/src/app/pages/pantry/pantry.page.html @@ -87,6 +87,9 @@ Shopping list is empty :( + + Total cost: R {{totalShoppingPrice}} + i.name !== item.name ); + this.calculateTotalPrice(); } }, error: (err) => { @@ -273,6 +277,7 @@ export class PantryPage implements OnInit, ViewWillEnter { (i) => i.name !== item.name ); this.errorHandlerService.presentSuccessToast('Item Bought!'); + this.calculateTotalPrice(); } } }, @@ -295,6 +300,15 @@ export class PantryPage implements OnInit, ViewWillEnter { }); } + calculateTotalPrice() { + this.totalShoppingPrice = 0; + this.shoppingItems.forEach((item) => { + if (item.price) { + this.totalShoppingPrice += item.price; + } + }); + } + closeSlidingItems() { this.foodListItem.forEach((item) => { item.closeItem(); From 96eb0c5a0024ee189bbed4fbc8c4e5468cb8fa95 Mon Sep 17 00:00:00 2001 From: Skulderlock <78735770+SkulderLock@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:28:22 +0200 Subject: [PATCH 19/97] =?UTF-8?q?=F0=9F=90=9B=20bug=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/pages/home/home.page.ts | 2 + frontend/src/app/pages/login/login.page.ts | 49 ++++++++++++------- .../recipe-book/add-recipe.service.ts | 7 +-- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/pages/home/home.page.ts b/frontend/src/app/pages/home/home.page.ts index 6fe1a632..4d8703f0 100644 --- a/frontend/src/app/pages/home/home.page.ts +++ b/frontend/src/app/pages/home/home.page.ts @@ -151,9 +151,11 @@ export class HomePage implements OnInit, ViewWillEnter { hideLoading() { this.showLoading = false; + this.isLoading = false; setTimeout(() => { this.showLoading = false; + this.isLoading = false; }, 200); } } diff --git a/frontend/src/app/pages/login/login.page.ts b/frontend/src/app/pages/login/login.page.ts index 0c6f9ce0..8688ddb1 100644 --- a/frontend/src/app/pages/login/login.page.ts +++ b/frontend/src/app/pages/login/login.page.ts @@ -3,7 +3,10 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { IonicModule } from '@ionic/angular'; import { Router } from '@angular/router'; -import { AuthenticationService, ErrorHandlerService } from '../../services/services'; +import { + AuthenticationService, + ErrorHandlerService, +} from '../../services/services'; import { UserI } from '../../models/user.model'; @Component({ @@ -11,30 +14,29 @@ import { UserI } from '../../models/user.model'; templateUrl: './login.page.html', styleUrls: ['./login.page.scss'], standalone: true, - imports: [IonicModule, CommonModule, FormsModule] + imports: [IonicModule, CommonModule, FormsModule], }) export class LoginPage implements OnInit { user: UserI = { username: '', email: '', password: '', - } - + }; - constructor(private router: Router, - private errorHandlerService: ErrorHandlerService, - private auth: AuthenticationService - ) { } + constructor( + private router: Router, + private errorHandlerService: ErrorHandlerService, + private auth: AuthenticationService + ) {} - ngOnInit() { - } + ngOnInit() {} async login(form: any) { const loginUser: UserI = { username: '', email: form.email, password: form.password, - } + }; this.auth.login(loginUser).subscribe({ next: (response) => { if (response.status == 200) { @@ -46,21 +48,32 @@ export class LoginPage implements OnInit { } }, error: (error) => { - if (error.status == 403){ - this.errorHandlerService.presentErrorToast('Invalid credentials', 'Invalid credentials'); + if (error.status == 403) { + this.errorHandlerService.presentErrorToast( + 'Invalid credentials', + 'Invalid credentials' + ); localStorage.removeItem('token'); - }else if(error.status == 404){ - this.errorHandlerService.presentErrorToast('Email or password incorrect', 'Email or password incorrect'); + } else if (error.status == 404) { + this.errorHandlerService.presentErrorToast( + 'Email or password incorrect', + 'Email or password incorrect' + ); localStorage.removeItem('token'); - }else{ - this.errorHandlerService.presentErrorToast('Unexpected error. Please try again', error); + } else { + this.errorHandlerService.presentErrorToast( + 'Unexpected error. Please try again', + error + ); } - } + }, }); } goToSignup() { this.router.navigate(['../signup']); + // this.router.navigate(['app/tabs/home']); + localStorage.removeItem('token'); } } diff --git a/frontend/src/app/services/recipe-book/add-recipe.service.ts b/frontend/src/app/services/recipe-book/add-recipe.service.ts index e29f5aee..901652e3 100644 --- a/frontend/src/app/services/recipe-book/add-recipe.service.ts +++ b/frontend/src/app/services/recipe-book/add-recipe.service.ts @@ -3,11 +3,12 @@ import { BehaviorSubject } from 'rxjs'; import { MealI } from '../../models/meal.model'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class AddRecipeService { - private recipeSource: BehaviorSubject = new BehaviorSubject(undefined); - constructor() { } + private recipeSource: BehaviorSubject = + new BehaviorSubject(undefined); + constructor() {} recipeItem$ = this.recipeSource.asObservable(); From 7eab97ffbd36669861c8452af6da00efabeeadd9 Mon Sep 17 00:00:00 2001 From: Skulderlock <78735770+SkulderLock@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:32:12 +0200 Subject: [PATCH 20/97] =?UTF-8?q?=E2=9E=96=20remove=20confilicting=20depen?= =?UTF-8?q?dencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + package-lock.json | 12 ------------ package.json | 1 - 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index caec0976..9db77445 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ $RECYCLE.BIN/ *.log log.txt mealmaestro-logs.txt +logs/ # ios diff --git a/package-lock.json b/package-lock.json index b981493c..3ce0d53c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "@ionic/angular": "^7.0.0", "@types/chart.js": "^2.9.37", "chart.js": "^4.3.0", - "cordova-plugin-advanced-http": "^3.3.1", "cordova-plugin-file": "^8.0.0", "dotenv": "^16.3.1", "ionicons": "^7.0.0", @@ -6111,17 +6110,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cordova-plugin-advanced-http": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/cordova-plugin-advanced-http/-/cordova-plugin-advanced-http-3.3.1.tgz", - "integrity": "sha512-hESuB3mxIHCUrzb5lm7juda6PSNcC5N8Invizj5wGV2rSldCapiNxMTEpzKR1UVPDDP2XOtBzO0SAYS+3+g/ig==", - "engines": [ - { - "name": "cordova", - "version": ">=4.0.0" - } - ] - }, "node_modules/cordova-plugin-file": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-8.0.0.tgz", diff --git a/package.json b/package.json index 936d32f6..3c326890 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@ionic/angular": "^7.0.0", "@types/chart.js": "^2.9.37", "chart.js": "^4.3.0", - "cordova-plugin-advanced-http": "^3.3.1", "cordova-plugin-file": "^8.0.0", "dotenv": "^16.3.1", "ionicons": "^7.0.0", From ee491a89dafd3df346659917100bfe90355bfb0b Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Mon, 18 Sep 2023 17:22:45 +0200 Subject: [PATCH 21/97] Update home.page.ts --- frontend/src/app/pages/home/home.page.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/pages/home/home.page.ts b/frontend/src/app/pages/home/home.page.ts index 39a7d341..3da718ba 100644 --- a/frontend/src/app/pages/home/home.page.ts +++ b/frontend/src/app/pages/home/home.page.ts @@ -104,6 +104,7 @@ export class HomePage implements OnInit, ViewWillEnter { await new Promise((resolve, reject) => { this.mealGenerationservice.getDailyMeals(date).subscribe({ next: (data) => { + console.log('Received data:', data); if (data.body) { let mealsForDay: DaysMealsI = { breakfast: undefined, From f64233cc1c11c8c541559393385d77d8836b29f2 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Tue, 19 Sep 2023 09:33:06 +0200 Subject: [PATCH 22/97] images fixed --- .../src/app/components/daily-meals/daily-meals.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/components/daily-meals/daily-meals.component.scss b/frontend/src/app/components/daily-meals/daily-meals.component.scss index cabdf0b2..71d263d8 100644 --- a/frontend/src/app/components/daily-meals/daily-meals.component.scss +++ b/frontend/src/app/components/daily-meals/daily-meals.component.scss @@ -18,6 +18,7 @@ ion-card { } .div1 img { + min-width: 100%; width: 100%; height: 100%; object-fit: cover; From 38fec0f09291d0662bce145b4a846d11fda7c112 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Tue, 19 Sep 2023 09:40:07 +0200 Subject: [PATCH 23/97] more css fixed --- .../app/components/daily-meals/daily-meals.component.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/components/daily-meals/daily-meals.component.scss b/frontend/src/app/components/daily-meals/daily-meals.component.scss index 71d263d8..7125e3dd 100644 --- a/frontend/src/app/components/daily-meals/daily-meals.component.scss +++ b/frontend/src/app/components/daily-meals/daily-meals.component.scss @@ -11,6 +11,7 @@ ion-item { width: 100%; display: block; --ion-padding: 0px; + } ion-card { padding: 0%; @@ -29,6 +30,11 @@ ion-card { --padding-start: 0; --padding-end: 0; padding-right: 0%; + --border-style:none; +} + +.item-inner { + border-style: none !important; } .side { display: inline; From 7b0d43191d25c8c9457cc51a84571c951c8d6bfb Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Tue, 19 Sep 2023 12:28:48 +0200 Subject: [PATCH 24/97] Create HydrationService.java --- .../fellowship/mealmaestro/services/HydrationService.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java new file mode 100644 index 00000000..fdd54976 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -0,0 +1,8 @@ +package fellowship.mealmaestro.services; + +import org.springframework.stereotype.Service; + +@Service +public class HydrationService { + +} From 5f156c211f2a6efcbcc89094f35a545cdafca9f9 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Tue, 19 Sep 2023 12:53:59 +0200 Subject: [PATCH 25/97] Create TriggerService.java --- .../fellowship/mealmaestro/services/TriggerService.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java b/backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java new file mode 100644 index 00000000..4a2e7f2f --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java @@ -0,0 +1,6 @@ +package fellowship.mealmaestro.services; + +public class TriggerService { + + +} From 225c5eea76ae68f3ccf649a7d3e71095c4005a5f Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Tue, 19 Sep 2023 15:21:26 +0200 Subject: [PATCH 26/97] Create RecommendationService.java --- .../fellowship/mealmaestro/services/RecommendationService.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java new file mode 100644 index 00000000..e69de29b From f46224a37b5d92a29c52274de97e3914334d3e9b Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:32:47 +0200 Subject: [PATCH 27/97] Update daily-meals.component.scss --- .../src/app/components/daily-meals/daily-meals.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/components/daily-meals/daily-meals.component.scss b/frontend/src/app/components/daily-meals/daily-meals.component.scss index 7125e3dd..f61d9637 100644 --- a/frontend/src/app/components/daily-meals/daily-meals.component.scss +++ b/frontend/src/app/components/daily-meals/daily-meals.component.scss @@ -24,6 +24,7 @@ ion-card { height: 100%; object-fit: cover; padding-right: 0px !important; + overflow: hidden; } .no-style { From affd6372be4fa3206439d7561868430b3bc446df Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:41:01 +0200 Subject: [PATCH 28/97] Create LogEntryModel.java --- .../fellowship/mealmaestro/models/neo4j/LogEntryModel.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java new file mode 100644 index 00000000..8934b059 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java @@ -0,0 +1,5 @@ +package fellowship.mealmaestro.models.neo4j; + +public class LogEntryModel { + +} From b33082fe7365270d04abea151f645ce66c88f27b Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:43:42 +0200 Subject: [PATCH 29/97] Update RecommendationService.java --- .../mealmaestro/services/RecommendationService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java index e69de29b..de60a33e 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java @@ -0,0 +1,5 @@ +package fellowship.mealmaestro.services; + +public class RecommendationService { + +} From df3bb9ac4e89e0526480bff5ebf70343ce383322 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:45:36 +0200 Subject: [PATCH 30/97] Relationship over new nodes --- .../fellowship/mealmaestro/models/neo4j/LogEntryModel.java | 5 ----- .../mealmaestro/models/neo4j/relationships/HasLogEntry.java | 0 2 files changed, 5 deletions(-) delete mode 100644 backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java create mode 100644 backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java deleted file mode 100644 index 8934b059..00000000 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/LogEntryModel.java +++ /dev/null @@ -1,5 +0,0 @@ -package fellowship.mealmaestro.models.neo4j; - -public class LogEntryModel { - -} diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java new file mode 100644 index 00000000..e69de29b From 18615f658ebd85ce5e3c58afedcd27cad4695182 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:47:29 +0200 Subject: [PATCH 31/97] Update HasLogEntry.java --- .../neo4j/relationships/HasLogEntry.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java index e69de29b..e383362d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java @@ -0,0 +1,62 @@ +package fellowship.mealmaestro.models.neo4j.relationships; + +import java.time.LocalDate; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.RelationshipProperties; +import org.springframework.data.neo4j.core.schema.TargetNode; + +import fellowship.mealmaestro.models.neo4j.MealModel; + +@RelationshipProperties +public class HasLogEntry { +@Id + @GeneratedValue + private Long id; + + @TargetNode + private MealModel meal; + + private LocalDate date; + + private String entryType; + + public HasLogEntry(MealModel meal, LocalDate date, String entryType) { + this.meal = meal; + this.date = date; + this.entryType = entryType; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public MealModel getMeal() { + return this.meal; + } + + public void setMeal(MealModel meal) { + this.meal = meal; + } + + public LocalDate getDate() { + return this.date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public String getEntryType() { + return this.entryType; + } + + public void setEntryType(String entryType) { + this.entryType = entryType; + } +} From bd64707c22ac8c9b28876a5c9c7073ed434e8b8b Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:51:32 +0200 Subject: [PATCH 32/97] added relationships to user --- .../mealmaestro/models/neo4j/UserModel.java | 12 ++++++++++++ .../models/neo4j/relationships/HasLogEntry.java | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java index 8da39dbb..a390a0f2 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java @@ -12,6 +12,7 @@ import org.springframework.security.core.userdetails.UserDetails; import fellowship.mealmaestro.models.auth.AuthorityRoleModel; +import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; import fellowship.mealmaestro.models.neo4j.relationships.HasMeal; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -54,6 +55,9 @@ public class UserModel implements UserDetails { @Relationship(type = "HAS_MEAL") private List meals; + @Relationship(type = "HAS_LOG_ENTRY") + private List entries; + public UserModel() { this.authorityRole = AuthorityRoleModel.USER; } @@ -167,4 +171,12 @@ public List getMeals() { public void setMeals(List meals) { this.meals = meals; } + + public List getLogEntries() { + return entries; + } + + public void setLogEntries(List entries) { + this.entries = entries; + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java index e383362d..e7afe458 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java @@ -11,7 +11,7 @@ @RelationshipProperties public class HasLogEntry { -@Id + @Id @GeneratedValue private Long id; From 8e14a743fad9b59d0f42532c595e1dc830cdc9ab Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:53:35 +0200 Subject: [PATCH 33/97] removed empty constructor --- .../mealmaestro/models/neo4j/relationships/HasMeal.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java index d526dcee..1aa85be8 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java @@ -22,9 +22,6 @@ public class HasMeal { private String mealType; - public HasMeal() { - } - public HasMeal(MealModel meal, LocalDate date, String mealType) { this.meal = meal; this.date = date; From f5c987267bdcfd56dac399700701efd68739b44c Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 09:53:49 +0200 Subject: [PATCH 34/97] mb gang --- .../mealmaestro/models/neo4j/relationships/HasMeal.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java index 1aa85be8..d526dcee 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasMeal.java @@ -22,6 +22,9 @@ public class HasMeal { private String mealType; + public HasMeal() { + } + public HasMeal(MealModel meal, LocalDate date, String mealType) { this.meal = meal; this.date = date; From a449e08977f207481e0128836fffcd8d30b8bbe5 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 10:31:44 +0200 Subject: [PATCH 35/97] Update MealManagementController.java --- .../controllers/MealManagementController.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java index bdd63bda..e25f6ddb 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java @@ -138,16 +138,4 @@ public ResponseEntity regenerate(@RequestBody RegenerateMealRequest r return ResponseEntity.ok(returnedMeal); } - // @GetMapping("/getPopularMeals") - // public String popularMeals() throws JsonMappingException, - // JsonProcessingException{ - // return mealManagementService.generatePopularMeals(); - // } - - // @GetMapping("/getSearchedMeals") - // public String searchedMeals(@RequestParam String query) throws - // JsonMappingException, JsonProcessingException { - // // Call the mealManagementService to search meals based on the query - // return mealManagementService.generateSearchedMeals(query); - // } } From a72c55e25b3271ff2e0d381f795fef7484f14a69 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 10:35:07 +0200 Subject: [PATCH 36/97] Create LogService.java LogService to handle creating storing and retrieving of entries --- .../java/fellowship/mealmaestro/services/LogService.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 backend/src/main/java/fellowship/mealmaestro/services/LogService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java new file mode 100644 index 00000000..aeb608d6 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -0,0 +1,8 @@ +package fellowship.mealmaestro.services; + +import org.springframework.stereotype.Service; + +@Service +public class LogService { + +} From ffac33e8125015aacf2125f53b1443ff246d20d6 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 10:52:37 +0200 Subject: [PATCH 37/97] added findbymealNameFor logservice --- .../mealmaestro/services/LogService.java | 13 ++++++++++++- .../services/MealDatabaseService.java | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java index aeb608d6..5a885b19 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -1,8 +1,19 @@ package fellowship.mealmaestro.services; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.stereotype.Service; +import fellowship.mealmaestro.models.neo4j.MealModel; +import fellowship.mealmaestro.models.neo4j.UserModel; + @Service public class LogService { - + @Autowired + private UserService userService; + + public void logMeal(String token, MealModel meal, String entryType){ + UserModel user = userService.getUser(token); + + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java index 515f6d0f..da4f2316 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealDatabaseService.java @@ -134,6 +134,23 @@ public Optional findMealTypeForUser(String type, String token) { return Optional.empty(); } + public Optional findMealForUser(String mealName, String token) { + String email = jwtService.extractUserEmail(token); + + UserModel user = userRepository.findByEmail(email).get(); + List randomMeals = mealRepository.get100RandomMeals(); + + // if meal with meal type is present in randomMeals, return it + for (MealModel meal : randomMeals) { + if (meal.getName().equals(mealName)) { + if (canMakeMeal(user.getPantry().getFoods(), meal.getIngredients())) { + return Optional.of(meal); + } + } + } + + return Optional.empty(); + } public boolean canMakeMeal(List pantryItems, String ingredients) { String[] ingredientsArray = ingredients.split(","); From be387d682fadd48b45c4e263931789409dea5fbe Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 11:06:20 +0200 Subject: [PATCH 38/97] logging of meals added --- .../mealmaestro/services/LogService.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java index 5a885b19..0c6d8592 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -1,19 +1,36 @@ package fellowship.mealmaestro.services; +import java.io.Console; +import java.time.LocalDate; +import java.util.Optional; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.neo4j.MealModel; import fellowship.mealmaestro.models.neo4j.UserModel; +import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; +import fellowship.mealmaestro.repositories.neo4j.UserRepository; @Service public class LogService { @Autowired private UserService userService; + @Autowired + private MealDatabaseService mealDatabaseService; + @Autowired + private UserRepository userRepository; public void logMeal(String token, MealModel meal, String entryType){ UserModel user = userService.getUser(token); - + Optional dbMeal = mealDatabaseService.findMealForUser(meal.getName(), token); + if(dbMeal.isPresent()) + { + HasLogEntry entry = new HasLogEntry(dbMeal.get(),LocalDate.now(), entryType); + user.getEntries().add(entry); + userRepository.save(user); + System.out.println("LogEntry saved! ("+ meal.getName() +","+ entryType +")"); + } } } From bd5d27cd76114521efb98416d1d894bd24571059 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 11:08:12 +0200 Subject: [PATCH 39/97] regen logged --- .../mealmaestro/controllers/MealManagementController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java index e25f6ddb..e44b72df 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java @@ -19,6 +19,7 @@ import fellowship.mealmaestro.models.RegenerateMealRequest; import fellowship.mealmaestro.models.neo4j.DateModel; import fellowship.mealmaestro.models.neo4j.MealModel; +import fellowship.mealmaestro.services.LogService; import fellowship.mealmaestro.services.MealDatabaseService; import fellowship.mealmaestro.services.MealManagementService; import jakarta.validation.Valid; @@ -29,6 +30,8 @@ public class MealManagementController { private MealManagementService mealManagementService; @Autowired private MealDatabaseService mealDatabaseService; + @Autowired + private LogService logService; @PostMapping("/getMealPlanForDay") public ResponseEntity> dailyMeals(@Valid @RequestBody DateModel request, @@ -121,7 +124,7 @@ public ResponseEntity regenerate(@RequestBody RegenerateMealRequest r throws JsonMappingException, JsonProcessingException { token = token.substring(7); - + logService.logMeal(token, request.getMeal(), "regenerate"); // Try find an appropriate meal in the database Optional replacementMeal = mealDatabaseService.findMealTypeForUser(request.getMeal().getType(), token); From 9d10ca9b2be19b0ca43918c789982b865fb089e7 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 11:46:27 +0200 Subject: [PATCH 40/97] Logging Functional --- .../controllers/MealManagementController.java | 4 ++- .../neo4j/relationships/HasLogEntry.java | 3 ++ .../mealmaestro/services/LogService.java | 28 +++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java index e44b72df..d00c4619 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/MealManagementController.java @@ -123,8 +123,10 @@ public ResponseEntity regenerate(@RequestBody RegenerateMealRequest r @RequestHeader("Authorization") String token) throws JsonMappingException, JsonProcessingException { - token = token.substring(7); logService.logMeal(token, request.getMeal(), "regenerate"); + + token = token.substring(7); + // Try find an appropriate meal in the database Optional replacementMeal = mealDatabaseService.findMealTypeForUser(request.getMeal().getType(), token); diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java index e7afe458..16548761 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java @@ -22,6 +22,9 @@ public class HasLogEntry { private String entryType; + public HasLogEntry() { + } + public HasLogEntry(MealModel meal, LocalDate date, String entryType) { this.meal = meal; this.date = date; diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java index 0c6d8592..74a9b735 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -11,6 +11,7 @@ import fellowship.mealmaestro.models.neo4j.MealModel; import fellowship.mealmaestro.models.neo4j.UserModel; import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; +import fellowship.mealmaestro.models.neo4j.relationships.HasMeal; import fellowship.mealmaestro.repositories.neo4j.UserRepository; @Service @@ -19,18 +20,29 @@ public class LogService { private UserService userService; @Autowired private MealDatabaseService mealDatabaseService; - @Autowired + @Autowired private UserRepository userRepository; - public void logMeal(String token, MealModel meal, String entryType){ + public void logMeal(String token, MealModel meal, String entryType) { UserModel user = userService.getUser(token); - Optional dbMeal = mealDatabaseService.findMealForUser(meal.getName(), token); - if(dbMeal.isPresent()) + MealModel dbMeal = null; + for(HasMeal m : user.getMeals()) + { + if(m.getMeal().getName().equals(meal.getName())) + { + dbMeal = m.getMeal(); + } + } + if(dbMeal == null) { - HasLogEntry entry = new HasLogEntry(dbMeal.get(),LocalDate.now(), entryType); - user.getEntries().add(entry); - userRepository.save(user); - System.out.println("LogEntry saved! ("+ meal.getName() +","+ entryType +")"); + System.out.println("logging failed"); + return; } + HasLogEntry entry = new HasLogEntry(dbMeal, LocalDate.now(), entryType); + user.getEntries().add(entry); + userRepository.save(user); + System.out.println("LogEntry saved! (" + meal.getName() + "," + entryType + ")"); + } + } From 173c8aca9995445f864fe4959716109397016cdd Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 11:48:20 +0200 Subject: [PATCH 41/97] cleanup --- .../java/fellowship/mealmaestro/services/LogService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java index 74a9b735..b7d4169b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -1,11 +1,7 @@ package fellowship.mealmaestro.services; -import java.io.Console; import java.time.LocalDate; -import java.util.Optional; - import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.neo4j.MealModel; @@ -19,8 +15,6 @@ public class LogService { @Autowired private UserService userService; @Autowired - private MealDatabaseService mealDatabaseService; - @Autowired private UserRepository userRepository; public void logMeal(String token, MealModel meal, String entryType) { From 9a3b7e27a85ead58868b2445ea41db9a153b5321 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 12:16:12 +0200 Subject: [PATCH 42/97] poll user list and set if processed. --- .../models/neo4j/relationships/HasLogEntry.java | 14 ++++++++++++++ .../repositories/neo4j/UserRepository.java | 4 ++++ .../mealmaestro/services/HydrationService.java | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java index 16548761..c5315bad 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/relationships/HasLogEntry.java @@ -22,6 +22,20 @@ public class HasLogEntry { private String entryType; + private boolean processed; + + public boolean isProcessed() { + return this.processed; + } + + public boolean getProcessed() { + return this.processed; + } + + public void setProcessed(boolean processed) { + this.processed = processed; + } + public HasLogEntry() { } diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java index 63492a05..34f983bf 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java @@ -1,5 +1,6 @@ package fellowship.mealmaestro.repositories.neo4j; +import java.util.List; import java.util.Optional; import org.springframework.data.neo4j.repository.Neo4jRepository; @@ -13,4 +14,7 @@ public interface UserRepository extends Neo4jRepository { @Query("MATCH (n0:User {email: $email}) SET n0.name = $name RETURN n0") UserModel updateUser(String email, String username); + + @Query("MATCH (u:User)-[r:HAS_LOG_ENTRY]->(logEntry) WHERE NOT EXISTS(r.processed) OR NOT r.processed RETURN DISTINCT u") + List findUsersWithNewLogEntries(); } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index fdd54976..15115ff6 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -1,8 +1,23 @@ package fellowship.mealmaestro.services; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import fellowship.mealmaestro.models.neo4j.UserModel; +import fellowship.mealmaestro.repositories.neo4j.UserRepository; +import java.util.List; + @Service public class HydrationService { - + @Autowired + private UserRepository userRepository; + + @Scheduled(fixedRate = 60 * 1000) + public void pollLogs() { + List userList = userRepository.findUsersWithNewLogEntries(); + + + } + } From 19771b38042db32c1f8953c0692c5c92f2a5f6f6 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:10:21 +0200 Subject: [PATCH 43/97] Tweaked poll rate --- .../fellowship/mealmaestro/services/HydrationService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 15115ff6..2a008047 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -13,10 +13,10 @@ public class HydrationService { @Autowired private UserRepository userRepository; - @Scheduled(fixedRate = 60 * 1000) + @Scheduled(fixedRate = 20 * 60 * 1000) public void pollLogs() { List userList = userRepository.findUsersWithNewLogEntries(); - + } From 8d1c23e01acea3496d955cf1dd4fb46f07c27f70 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:10:26 +0200 Subject: [PATCH 44/97] Create ViewModel.java --- .../mealmaestro/models/ViewModel.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java diff --git a/backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java new file mode 100644 index 00000000..17484b16 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java @@ -0,0 +1,27 @@ +package fellowship.mealmaestro.models; + +import java.util.HashMap; + +import org.springframework.data.neo4j.core.schema.Node; + +@Node("View") +public class ViewModel { + private HashMap ScoreMap; + + public ViewModel() { + } + + public ViewModel(HashMap ScoreMap) { + this.ScoreMap = ScoreMap; + } + + public HashMap getScoreMap() { + return this.ScoreMap; + } + + public void setScoreMap(HashMap ScoreMap) { + this.ScoreMap = ScoreMap; + } + + +} From 55eb4ff310687ab2bb1e48e961e91daece258a40 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:11:05 +0200 Subject: [PATCH 45/97] moved to correct folder --- .../fellowship/mealmaestro/models/{ => neo4j}/ViewModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename backend/src/main/java/fellowship/mealmaestro/models/{ => neo4j}/ViewModel.java (91%) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java similarity index 91% rename from backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java rename to backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index 17484b16..0c1e90f7 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -1,4 +1,4 @@ -package fellowship.mealmaestro.models; +package fellowship.mealmaestro.models.neo4j; import java.util.HashMap; From 7b4b5d94f405e1440183640b5782d084efb28e3c Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:18:41 +0200 Subject: [PATCH 46/97] updated models to support view --- .../mealmaestro/models/neo4j/UserModel.java | 27 +++++++++++++++++++ .../mealmaestro/models/neo4j/ViewModel.java | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java index a390a0f2..119e3527 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java @@ -58,6 +58,33 @@ public class UserModel implements UserDetails { @Relationship(type = "HAS_LOG_ENTRY") private List entries; + @Relationship(type = "HAS_VIEW", direction = Relationship.Direction.OUTGOING) + private ViewModel view; + + public Long getVersion() { + return this.version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public List getEntries() { + return this.entries; + } + + public void setEntries(List entries) { + this.entries = entries; + } + + public ViewModel getView() { + return this.view; + } + + public void setView(ViewModel view) { + this.view = view; + } + public UserModel() { this.authorityRole = AuthorityRoleModel.USER; } diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index 0c1e90f7..cde88f1e 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -1,11 +1,11 @@ package fellowship.mealmaestro.models.neo4j; import java.util.HashMap; - import org.springframework.data.neo4j.core.schema.Node; @Node("View") public class ViewModel { + private HashMap ScoreMap; public ViewModel() { From 84eb71cd71fe2e557d15ac8f5c4d957beb0adeae Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:19:47 +0200 Subject: [PATCH 47/97] ensure hashmap exists --- .../java/fellowship/mealmaestro/models/neo4j/ViewModel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index cde88f1e..dbf505f6 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -5,8 +5,8 @@ @Node("View") public class ViewModel { - - private HashMap ScoreMap; + + private HashMap ScoreMap = new HashMap<>(); public ViewModel() { } From 7f688fea6ebd2585abc8be8379a1a8b9ad099ad3 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:31:29 +0200 Subject: [PATCH 48/97] added ability to find a users unprocessed log entries --- .../repositories/neo4j/UserRepository.java | 6 +++++- .../mealmaestro/services/LogService.java | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java index 34f983bf..f7dfa887 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.neo4j.repository.query.Query; import fellowship.mealmaestro.models.neo4j.UserModel; +import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; public interface UserRepository extends Neo4jRepository { @@ -14,7 +15,10 @@ public interface UserRepository extends Neo4jRepository { @Query("MATCH (n0:User {email: $email}) SET n0.name = $name RETURN n0") UserModel updateUser(String email, String username); - + @Query("MATCH (u:User)-[r:HAS_LOG_ENTRY]->(logEntry) WHERE NOT EXISTS(r.processed) OR NOT r.processed RETURN DISTINCT u") List findUsersWithNewLogEntries(); + + @Query("MATCH (user:User {id: $user.id})-[:HAS_LOG_ENTRY]->(logEntry:HasLogEntry) WHERE NOT logEntry.processed RETURN logEntry") + List findUnprocessedLogEntriesForUser(UserModel user); } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java index b7d4169b..30d5a565 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -1,6 +1,8 @@ package fellowship.mealmaestro.services; import java.time.LocalDate; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -20,18 +22,15 @@ public class LogService { public void logMeal(String token, MealModel meal, String entryType) { UserModel user = userService.getUser(token); MealModel dbMeal = null; - for(HasMeal m : user.getMeals()) - { - if(m.getMeal().getName().equals(meal.getName())) - { + for (HasMeal m : user.getMeals()) { + if (m.getMeal().getName().equals(meal.getName())) { dbMeal = m.getMeal(); } } - if(dbMeal == null) - { - System.out.println("logging failed"); - return; - } + if (dbMeal == null) { + System.out.println("logging failed"); + return; + } HasLogEntry entry = new HasLogEntry(dbMeal, LocalDate.now(), entryType); user.getEntries().add(entry); userRepository.save(user); @@ -39,4 +38,7 @@ public void logMeal(String token, MealModel meal, String entryType) { } + public List findUnprocessedLogEntriesForUser(UserModel user) { + return userRepository.findUnprocessedLogEntriesForUser(user); + } } From fa0bedf7cf45cc535b1cec82da50c67bfdd2d75e Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 13:46:26 +0200 Subject: [PATCH 49/97] update view model and efficient processing --- .../mealmaestro/models/neo4j/ViewModel.java | 21 ++++++++++++++----- .../services/HydrationService.java | 10 ++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index dbf505f6..a5989e0f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -3,25 +3,36 @@ import java.util.HashMap; import org.springframework.data.neo4j.core.schema.Node; +import lombok.Data; + @Node("View") public class ViewModel { + @Data + private class StatValues { + public Double Score; + public Double minScore; + public Double maxScore; + } - private HashMap ScoreMap = new HashMap<>(); + private HashMap ScoreMap = new HashMap<>(); public ViewModel() { } - public ViewModel(HashMap ScoreMap) { + public ViewModel(HashMap ScoreMap) { this.ScoreMap = ScoreMap; } - public HashMap getScoreMap() { + public HashMap getScoreMap() { return this.ScoreMap; } - public void setScoreMap(HashMap ScoreMap) { + public void setScoreMap(HashMap ScoreMap) { this.ScoreMap = ScoreMap; } + + public void updateScore(String ingredient, Double Score){ + + } - } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 2a008047..0543113a 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.neo4j.UserModel; +import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; import fellowship.mealmaestro.repositories.neo4j.UserRepository; import java.util.List; @@ -16,7 +17,14 @@ public class HydrationService { @Scheduled(fixedRate = 20 * 60 * 1000) public void pollLogs() { List userList = userRepository.findUsersWithNewLogEntries(); - + //per user + for(UserModel user : userList){ + List logEntries = userRepository.findUnprocessedLogEntriesForUser(user); + for(HasLogEntry entry : logEntries) + { + + } + } } From 79c8ba95ca0d1ce5b87fe22dee56bd308aad9e0a Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 15:27:22 +0200 Subject: [PATCH 50/97] data procesing and normalisation for hashmap --- .../mealmaestro/models/neo4j/ViewModel.java | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index a5989e0f..eb500dc6 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -1,6 +1,9 @@ package fellowship.mealmaestro.models.neo4j; import java.util.HashMap; + +import javax.script.ScriptEngine; + import org.springframework.data.neo4j.core.schema.Node; import lombok.Data; @@ -8,31 +11,65 @@ @Node("View") public class ViewModel { @Data - private class StatValues { - public Double Score; - public Double minScore; - public Double maxScore; + private class Scores { + public Double score; + public Double nScore; } - - private HashMap ScoreMap = new HashMap<>(); + private HashMap ScoreMap = new HashMap<>(); + private Double max; + private Double min; public ViewModel() { } - public ViewModel(HashMap ScoreMap) { + public ViewModel(HashMap ScoreMap) { this.ScoreMap = ScoreMap; } - public HashMap getScoreMap() { + public HashMap getScoreMap() { return this.ScoreMap; } - public void setScoreMap(HashMap ScoreMap) { + public void setScoreMap(HashMap ScoreMap) { this.ScoreMap = ScoreMap; } - - public void updateScore(String ingredient, Double Score){ + public void updateScore(String ingredient, Double Score) { + if (ScoreMap.containsKey(ingredient)) { + Boolean changed = false; + Scores scores = ScoreMap.get(ingredient); + scores.score += Score; + if (Score > max) { + max = Score; + changed = true; + } + + if (Score < min) { + min = Score; + changed = true; + } + scores.nScore = normalise(scores.score); + if(changed){ + normalise(); + } + + } else { + Scores scores = new Scores(); + scores.score = Score; + scores.nScore = 0.0; + ScoreMap.put(ingredient, scores); + } + } + + public Double normalise(Double Score) { + return 2 * ((Score - min) / (max - min)) - 1; + + } + public void normalise() { + // return 2 * ((Score - min) / (max - min)) - 1; + for(Scores scores : ScoreMap.values()){ + scores.nScore = 2 * ((scores.score - min) / (max - min)) - 1; + } } } From 79739c0bd64e3fe5dcac97763c2c2e595e84b4d9 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 15:27:47 +0200 Subject: [PATCH 51/97] cleanup --- .../mealmaestro/models/neo4j/ViewModel.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index eb500dc6..def60301 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -1,11 +1,7 @@ package fellowship.mealmaestro.models.neo4j; import java.util.HashMap; - -import javax.script.ScriptEngine; - import org.springframework.data.neo4j.core.schema.Node; - import lombok.Data; @Node("View") @@ -15,6 +11,7 @@ private class Scores { public Double score; public Double nScore; } + private HashMap ScoreMap = new HashMap<>(); private Double max; private Double min; @@ -49,7 +46,7 @@ public void updateScore(String ingredient, Double Score) { changed = true; } scores.nScore = normalise(scores.score); - if(changed){ + if (changed) { normalise(); } @@ -65,11 +62,12 @@ public Double normalise(Double Score) { return 2 * ((Score - min) / (max - min)) - 1; } + public void normalise() { - // return 2 * ((Score - min) / (max - min)) - 1; - for(Scores scores : ScoreMap.values()){ + // return 2 * ((Score - min) / (max - min)) - 1; + for (Scores scores : ScoreMap.values()) { scores.nScore = 2 * ((scores.score - min) / (max - min)) - 1; - } + } } } From 4d403de06db6ded5647c34ac40053b1f19262152 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 15:31:54 +0200 Subject: [PATCH 52/97] flesh out hydration system, i take break now --- .../services/HydrationService.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 0543113a..387fa57d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import fellowship.mealmaestro.models.neo4j.UserModel; +import fellowship.mealmaestro.models.neo4j.ViewModel; import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; import fellowship.mealmaestro.repositories.neo4j.UserRepository; import java.util.List; @@ -20,12 +21,29 @@ public void pollLogs() { //per user for(UserModel user : userList){ List logEntries = userRepository.findUnprocessedLogEntriesForUser(user); + ViewModel viewModel = user.getView(); + for(HasLogEntry entry : logEntries) { - + String ingredientString = entry.getMeal().getIngredients(); + //trim ingredient list + + //convert to List + + //Scores ++ * multiplier + + //update view model + + //set processed + entry.setProcessed(true); } } } - + // helper functions to be done + //trim + + //convert + + //scores } From 8a7233262510fdde0b53d1f22242b4e08e90f2d0 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 16:51:26 +0200 Subject: [PATCH 53/97] View Creation --- .../services/HydrationService.java | 68 +++++++++++++++---- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 387fa57d..aa30cc9b 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -8,6 +8,8 @@ import fellowship.mealmaestro.models.neo4j.ViewModel; import fellowship.mealmaestro.models.neo4j.relationships.HasLogEntry; import fellowship.mealmaestro.repositories.neo4j.UserRepository; + +import java.util.Arrays; import java.util.List; @Service @@ -18,32 +20,70 @@ public class HydrationService { @Scheduled(fixedRate = 20 * 60 * 1000) public void pollLogs() { List userList = userRepository.findUsersWithNewLogEntries(); - //per user - for(UserModel user : userList){ + // per user + for (UserModel user : userList) { List logEntries = userRepository.findUnprocessedLogEntriesForUser(user); ViewModel viewModel = user.getView(); - for(HasLogEntry entry : logEntries) - { - String ingredientString = entry.getMeal().getIngredients(); - //trim ingredient list - - //convert to List + for (HasLogEntry entry : logEntries) { - //Scores ++ * multiplier + String ingredientString = entry.getMeal().getIngredients(); + // trim ingredient list + ingredientString = trimCharacters(ingredientString); + // convert to List + List ingredientList = parseCommaSeparatedString(ingredientString); + // Scores ++ * multiplier + Double S_MULTIPLIER = getScoreValue(entry.getEntryType()); - //update view model + // update view model + for (String ingredient : ingredientList) { + viewModel.updateScore(ingredient, S_MULTIPLIER); + } - //set processed + // set processed entry.setProcessed(true); } + + user.setView(viewModel); + userRepository.save(user); } } + // helper functions to be done - //trim + // trim + private static String trimCharacters(String input) { + String regex = "[0-9\\s]+"; + String result = input.replaceAll(regex, ""); + return result; + } - //convert + // convert + private static List parseCommaSeparatedString(String input) { + String[] elements = input.split(","); + List result = Arrays.asList(elements); - //scores + return result; + } + + // scores + private static Double getScoreValue(String entryType) { + switch (entryType.toLowerCase()) { + case "regenerated": + return -0.9; + + case "like": + return 1.0; + + case "dislike": + return -0.5; + + case "save": + return 0.7; + + default: + return 0.5; + + } + } } From c6a3c3a7e9a3f90e03ff8244c000e8b62cfcd63a Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 16:58:32 +0200 Subject: [PATCH 54/97] Delete TriggerService.java --- .../fellowship/mealmaestro/services/TriggerService.java | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java diff --git a/backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java b/backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java deleted file mode 100644 index 4a2e7f2f..00000000 --- a/backend/src/main/java/fellowship/mealmaestro/services/TriggerService.java +++ /dev/null @@ -1,6 +0,0 @@ -package fellowship.mealmaestro.services; - -public class TriggerService { - - -} From aeaefec0dc7d931291b092f32941484246227fa2 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 16:59:37 +0200 Subject: [PATCH 55/97] done for the day i cant think --- .../mealmaestro/services/RecommendationService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java index de60a33e..e0ed7af4 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java @@ -1,5 +1,10 @@ package fellowship.mealmaestro.services; +import org.springframework.beans.factory.annotation.Autowired; + +import fellowship.mealmaestro.repositories.neo4j.UserRepository; + public class RecommendationService { - + @Autowired + private UserRepository userRepository; } From fd321446b21906d971a151d1c87170d0e6213483 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Thu, 21 Sep 2023 17:01:52 +0200 Subject: [PATCH 56/97] i lied, save request mapped, now im done --- .../mealmaestro/controllers/RecipeBookController.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/RecipeBookController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/RecipeBookController.java index 69c64210..131eb55e 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/RecipeBookController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/RecipeBookController.java @@ -1,9 +1,11 @@ package fellowship.mealmaestro.controllers; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import fellowship.mealmaestro.models.neo4j.MealModel; +import fellowship.mealmaestro.services.LogService; import fellowship.mealmaestro.services.RecipeBookService; import jakarta.validation.Valid; @@ -11,7 +13,8 @@ @RestController public class RecipeBookController { - + @Autowired + private LogService logService; private final RecipeBookService recipeBookService; public RecipeBookController(RecipeBookService recipeBookService) { @@ -25,6 +28,8 @@ public ResponseEntity addRecipe(@Valid @RequestBody MealModel request return ResponseEntity.badRequest().build(); } + logService.logMeal(token, request, "save"); + String authToken = token.substring(7); return ResponseEntity.ok(recipeBookService.addRecipe(request, authToken)); } From ddcfba98771c9ccda9351d622fd61aafe08ac332 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Fri, 22 Sep 2023 10:43:34 +0200 Subject: [PATCH 57/97] fixed build error --- .../mealmaestro/models/neo4j/ViewModel.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index def60301..45edbd9d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -1,11 +1,22 @@ package fellowship.mealmaestro.models.neo4j; import java.util.HashMap; + +import org.springframework.data.neo4j.core.schema.GeneratedValue; +import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import lombok.Data; - +import lombok.Getter; +import lombok.Setter; +@Getter +@Setter @Node("View") public class ViewModel { + + @Id + @GeneratedValue + private Long id; + @Data private class Scores { public Double score; From 9a2f03d38101a4c004b3dbc056a1d8fdde4e09f6 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Fri, 22 Sep 2023 10:55:58 +0200 Subject: [PATCH 58/97] score tweaks --- .../fellowship/mealmaestro/services/HydrationService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index aa30cc9b..f6e11c30 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -70,13 +70,13 @@ private static List parseCommaSeparatedString(String input) { private static Double getScoreValue(String entryType) { switch (entryType.toLowerCase()) { case "regenerated": - return -0.9; + return -0.4; case "like": return 1.0; case "dislike": - return -0.5; + return -0.7; case "save": return 0.7; From e8e27adbdb7ddb0e23b9e5af073d0dffbe4ac3c5 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Fri, 22 Sep 2023 11:07:09 +0200 Subject: [PATCH 59/97] enabled scheduliung and lowered polling rate --- .../java/fellowship/mealmaestro/MealmaestroApplication.java | 3 ++- .../java/fellowship/mealmaestro/services/HydrationService.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java b/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java index 6f094f5f..730c79b9 100644 --- a/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java +++ b/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java @@ -3,10 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; import com.fasterxml.jackson.core.JsonProcessingException; - +@EnableScheduling @SpringBootApplication public class MealmaestroApplication { diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index f6e11c30..1062c4f6 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -17,7 +17,7 @@ public class HydrationService { @Autowired private UserRepository userRepository; - @Scheduled(fixedRate = 20 * 60 * 1000) + @Scheduled(fixedRate = 1 * 60 * 1000) public void pollLogs() { List userList = userRepository.findUsersWithNewLogEntries(); // per user From 6da0b3d4b2ba0cd390b0b40ea6d881dcd2d6fb50 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Fri, 22 Sep 2023 12:20:01 +0200 Subject: [PATCH 60/97] many fixes --- .../mealmaestro/MealmaestroApplication.java | 2 +- .../mealmaestro/models/neo4j/UserModel.java | 2 +- .../mealmaestro/models/neo4j/ViewModel.java | 42 ++++++++----------- .../repositories/neo4j/UserRepository.java | 4 +- .../services/HydrationService.java | 19 +++++---- .../mealmaestro/services/LogService.java | 3 +- 6 files changed, 35 insertions(+), 37 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java b/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java index 730c79b9..a8e02c5d 100644 --- a/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java +++ b/backend/src/main/java/fellowship/mealmaestro/MealmaestroApplication.java @@ -7,8 +7,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; -@EnableScheduling @SpringBootApplication +@EnableScheduling public class MealmaestroApplication { public static void main(String[] args) throws JsonProcessingException { diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java index 119e3527..f8612a33 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java @@ -55,7 +55,7 @@ public class UserModel implements UserDetails { @Relationship(type = "HAS_MEAL") private List meals; - @Relationship(type = "HAS_LOG_ENTRY") + @Relationship(type = "HAS_LOG_ENTRY", direction = Relationship.Direction.OUTGOING) private List entries; @Relationship(type = "HAS_VIEW", direction = Relationship.Direction.OUTGOING) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index 45edbd9d..3f87720f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -6,10 +6,7 @@ import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import lombok.Data; -import lombok.Getter; -import lombok.Setter; -@Getter -@Setter + @Node("View") public class ViewModel { @@ -17,55 +14,50 @@ public class ViewModel { @GeneratedValue private Long id; - @Data - private class Scores { - public Double score; - public Double nScore; - } - - private HashMap ScoreMap = new HashMap<>(); + private HashMap ScoreMap = new HashMap<>(); + private HashMap nScoreMap = new HashMap<>(); private Double max; private Double min; public ViewModel() { } - public ViewModel(HashMap ScoreMap) { + public ViewModel(HashMap ScoreMap) { this.ScoreMap = ScoreMap; } - public HashMap getScoreMap() { + public HashMap getScoreMap() { return this.ScoreMap; } - public void setScoreMap(HashMap ScoreMap) { + public void setScoreMap(HashMap ScoreMap) { this.ScoreMap = ScoreMap; } public void updateScore(String ingredient, Double Score) { if (ScoreMap.containsKey(ingredient)) { Boolean changed = false; - Scores scores = ScoreMap.get(ingredient); - scores.score += Score; - if (Score > max) { + Double score = ScoreMap.get(ingredient); + Double nScore = nScoreMap.get(ingredient); + score += Score; + if (Score > max || max == null) { max = Score; changed = true; } - if (Score < min) { + if (Score < min || min == null) { min = Score; changed = true; } - scores.nScore = normalise(scores.score); + nScore = normalise(Score); if (changed) { normalise(); } } else { - Scores scores = new Scores(); - scores.score = Score; - scores.nScore = 0.0; - ScoreMap.put(ingredient, scores); + + ScoreMap.put(ingredient, Score); + nScoreMap.put(ingredient,normalise(Score)); } } @@ -76,8 +68,8 @@ public Double normalise(Double Score) { public void normalise() { // return 2 * ((Score - min) / (max - min)) - 1; - for (Scores scores : ScoreMap.values()) { - scores.nScore = 2 * ((scores.score - min) / (max - min)) - 1; + for (String ingredient : ScoreMap.keySet()) { + nScoreMap.put(ingredient, 2 * ((ScoreMap.get(ingredient) - min) / (max - min)) - 1) ; } } diff --git a/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java b/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java index f7dfa887..67ee04d8 100644 --- a/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java +++ b/backend/src/main/java/fellowship/mealmaestro/repositories/neo4j/UserRepository.java @@ -16,9 +16,9 @@ public interface UserRepository extends Neo4jRepository { @Query("MATCH (n0:User {email: $email}) SET n0.name = $name RETURN n0") UserModel updateUser(String email, String username); - @Query("MATCH (u:User)-[r:HAS_LOG_ENTRY]->(logEntry) WHERE NOT EXISTS(r.processed) OR NOT r.processed RETURN DISTINCT u") + @Query("MATCH (u:User)-[r:HAS_LOG_ENTRY]->(logEntry) WHERE r.processed IS NULL OR NOT r.processed RETURN DISTINCT u") List findUsersWithNewLogEntries(); - @Query("MATCH (user:User {id: $user.id})-[:HAS_LOG_ENTRY]->(logEntry:HasLogEntry) WHERE NOT logEntry.processed RETURN logEntry") + @Query("MATCH (user:User {id: $user.id})-[:HAS_LOG_ENTRY]->(logEntry) WHERE NOT logEntry.processed RETURN logEntry") List findUnprocessedLogEntriesForUser(UserModel user); } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 1062c4f6..0eb95306 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import fellowship.mealmaestro.models.neo4j.UserModel; import fellowship.mealmaestro.models.neo4j.ViewModel; @@ -16,15 +17,19 @@ public class HydrationService { @Autowired private UserRepository userRepository; - + @Transactional @Scheduled(fixedRate = 1 * 60 * 1000) public void pollLogs() { List userList = userRepository.findUsersWithNewLogEntries(); // per user - for (UserModel user : userList) { + for (UserModel nuser : userList) { + UserModel user = userRepository.findByEmail(nuser.getEmail()).get(); List logEntries = userRepository.findUnprocessedLogEntriesForUser(user); ViewModel viewModel = user.getView(); - + if(viewModel == null) + { + viewModel = new ViewModel(); + } for (HasLogEntry entry : logEntries) { String ingredientString = entry.getMeal().getIngredients(); @@ -42,8 +47,9 @@ public void pollLogs() { // set processed entry.setProcessed(true); - } + } + user.setEntries(logEntries); user.setView(viewModel); userRepository.save(user); } @@ -61,9 +67,8 @@ private static String trimCharacters(String input) { // convert private static List parseCommaSeparatedString(String input) { String[] elements = input.split(","); - List result = Arrays.asList(elements); - - return result; + + return Arrays.asList(elements); } // scores diff --git a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java index 30d5a565..d0bd4371 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/LogService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/LogService.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import fellowship.mealmaestro.models.neo4j.MealModel; import fellowship.mealmaestro.models.neo4j.UserModel; @@ -18,7 +19,7 @@ public class LogService { private UserService userService; @Autowired private UserRepository userRepository; - + @Transactional public void logMeal(String token, MealModel meal, String entryType) { UserModel user = userService.getUser(token); MealModel dbMeal = null; From 028608422ac7e593cf646497e4c55e9e60286bbb Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Fri, 22 Sep 2023 13:17:28 +0200 Subject: [PATCH 61/97] working now, lottta fixes --- .../mealmaestro/models/neo4j/UserModel.java | 4 +- .../mealmaestro/models/neo4j/ViewModel.java | 74 ++++++++++++++----- .../services/HydrationService.java | 7 +- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java index f8612a33..83a25ec5 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/UserModel.java @@ -55,10 +55,10 @@ public class UserModel implements UserDetails { @Relationship(type = "HAS_MEAL") private List meals; - @Relationship(type = "HAS_LOG_ENTRY", direction = Relationship.Direction.OUTGOING) + @Relationship(type = "HAS_LOG_ENTRY") private List entries; - @Relationship(type = "HAS_VIEW", direction = Relationship.Direction.OUTGOING) + @Relationship(type = "HAS_VIEW") private ViewModel view; public Long getVersion() { diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index 3f87720f..b07d1042 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -5,7 +5,6 @@ import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; -import lombok.Data; @Node("View") public class ViewModel { @@ -14,39 +13,44 @@ public class ViewModel { @GeneratedValue private Long id; - private HashMap ScoreMap = new HashMap<>(); - private HashMap nScoreMap = new HashMap<>(); - private Double max; - private Double min; + String[] Keys; + Double[] scoreValues; + Double[] nScoreValues; + + private Double max =0.0; + private Double min =0.0; public ViewModel() { } - public ViewModel(HashMap ScoreMap) { - this.ScoreMap = ScoreMap; - } public HashMap getScoreMap() { - return this.ScoreMap; + return arraysToHashMap(Keys, nScoreValues); } - public void setScoreMap(HashMap ScoreMap) { - this.ScoreMap = ScoreMap; - } public void updateScore(String ingredient, Double Score) { + HashMap ScoreMap = new HashMap<>(); + HashMap nScoreMap = new HashMap<>(); + + if(Keys != null){ + ScoreMap = arraysToHashMap(Keys, scoreValues); + nScoreMap = arraysToHashMap(Keys, nScoreValues); + } + + if (ScoreMap.containsKey(ingredient)) { Boolean changed = false; Double score = ScoreMap.get(ingredient); Double nScore = nScoreMap.get(ingredient); score += Score; - if (Score > max || max == null) { - max = Score; + if (score > max || max == null) { + max = score; changed = true; } - if (Score < min || min == null) { - min = Score; + if (score < min || min == null) { + min = score; changed = true; } nScore = normalise(Score); @@ -54,11 +58,17 @@ public void updateScore(String ingredient, Double Score) { normalise(); } + ScoreMap.put(ingredient, score); + nScoreMap.put(ingredient, nScore); + } else { - + ScoreMap.put(ingredient, Score); - nScoreMap.put(ingredient,normalise(Score)); + nScoreMap.put(ingredient, normalise(Score)); } + Keys = hashMapKeysToArray(nScoreMap); + scoreValues = hashMapValuesToArray(ScoreMap); + nScoreValues = hashMapValuesToArray(nScoreMap); } public Double normalise(Double Score) { @@ -68,9 +78,35 @@ public Double normalise(Double Score) { public void normalise() { // return 2 * ((Score - min) / (max - min)) - 1; + HashMap ScoreMap = new HashMap<>(); + HashMap nScoreMap = new HashMap<>(); for (String ingredient : ScoreMap.keySet()) { - nScoreMap.put(ingredient, 2 * ((ScoreMap.get(ingredient) - min) / (max - min)) - 1) ; + nScoreMap.put(ingredient, 2 * ((ScoreMap.get(ingredient) - min) / (max - min)) - 1); + } + Keys = hashMapKeysToArray(nScoreMap); + scoreValues = hashMapValuesToArray(ScoreMap); + nScoreValues = hashMapValuesToArray(nScoreMap); + } + + public static Double[] hashMapValuesToArray(HashMap hashMap) { + return hashMap.values().toArray(new Double[0]); + } + + public static String[] hashMapKeysToArray(HashMap hashMap) { + return hashMap.keySet().toArray(new String[0]); + } + + public static HashMap arraysToHashMap(String[] keys, Double[] values) { + if (keys.length != values.length) { + throw new IllegalArgumentException("Keys and values arrays must have the same length."); + } + + HashMap hashMap = new HashMap<>(); + for (int i = 0; i < keys.length; i++) { + hashMap.put(keys[i], values[i]); } + return hashMap; } + } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 0eb95306..85a0ada3 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -30,8 +30,8 @@ public void pollLogs() { { viewModel = new ViewModel(); } - for (HasLogEntry entry : logEntries) { - + for (int i = 0; i < user.getEntries().size();i++) { + HasLogEntry entry = user.getEntries().remove(i); String ingredientString = entry.getMeal().getIngredients(); // trim ingredient list ingredientString = trimCharacters(ingredientString); @@ -47,9 +47,8 @@ public void pollLogs() { // set processed entry.setProcessed(true); - + user.getEntries().add(entry); } - user.setEntries(logEntries); user.setView(viewModel); userRepository.save(user); } From f8396c5349f88ac0011958c935c252dfb147906b Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Fri, 22 Sep 2023 13:58:18 +0200 Subject: [PATCH 62/97] Update HydrationService.java --- .../java/fellowship/mealmaestro/services/HydrationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index 85a0ada3..cfbeb164 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -24,7 +24,6 @@ public void pollLogs() { // per user for (UserModel nuser : userList) { UserModel user = userRepository.findByEmail(nuser.getEmail()).get(); - List logEntries = userRepository.findUnprocessedLogEntriesForUser(user); ViewModel viewModel = user.getView(); if(viewModel == null) { From d1bf894b3221ca304bb9025ad95d066fa6f6c2ec Mon Sep 17 00:00:00 2001 From: Skulderlock <78735770+SkulderLock@users.noreply.github.com> Date: Sat, 23 Sep 2023 11:54:18 +0200 Subject: [PATCH 63/97] =?UTF-8?q?=E2=9C=A8=20New=20exception=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/GlobalExceptionHandler.java | 11 ++++--- .../mealmaestro/config/SecurityConfig.java | 31 +++++++++---------- .../exceptions/UserNotFoundException.java | 7 +++++ .../controllers/UserController.java | 3 +- .../mealmaestro/services/UserService.java | 5 +-- .../services/auth/AuthenticationService.java | 3 +- 6 files changed, 34 insertions(+), 26 deletions(-) create mode 100644 backend/src/main/java/fellowship/mealmaestro/config/exceptions/UserNotFoundException.java diff --git a/backend/src/main/java/fellowship/mealmaestro/config/GlobalExceptionHandler.java b/backend/src/main/java/fellowship/mealmaestro/config/GlobalExceptionHandler.java index 5042f310..bdb1a7ec 100644 --- a/backend/src/main/java/fellowship/mealmaestro/config/GlobalExceptionHandler.java +++ b/backend/src/main/java/fellowship/mealmaestro/config/GlobalExceptionHandler.java @@ -5,12 +5,13 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import fellowship.mealmaestro.config.exceptions.UserNotFoundException; + @RestControllerAdvice public class GlobalExceptionHandler { - // @ExceptionHandler(RuntimeException.class) - // public ResponseEntity handleUserNotFoundException(RuntimeException e) - // { - // return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND); - // } + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handleUserNotFoundException(UserNotFoundException e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND); + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/config/SecurityConfig.java b/backend/src/main/java/fellowship/mealmaestro/config/SecurityConfig.java index e2066b59..29f2f32f 100644 --- a/backend/src/main/java/fellowship/mealmaestro/config/SecurityConfig.java +++ b/backend/src/main/java/fellowship/mealmaestro/config/SecurityConfig.java @@ -22,34 +22,31 @@ public class SecurityConfig { private final AuthenticationProvider authenticationProvider; - public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, AuthenticationProvider authenticationProvider){ + public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, AuthenticationProvider authenticationProvider) { this.jwtAuthFilter = jwtAuthFilter; this.authenticationProvider = authenticationProvider; } - + @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - .csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authReq -> authReq - .requestMatchers("/register", "/authenticate") - .permitAll() - .anyRequest() - .authenticated() - ) - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) - .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); - + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(authReq -> authReq + .requestMatchers("/register", "/authenticate", "/hello") + .permitAll() + .anyRequest() + .authenticated()) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); http.authenticationProvider(authenticationProvider); return http.build(); } @Bean - CorsConfigurationSource corsConfigurationSource(){ + CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowedOrigins(Arrays.asList("*")); corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); diff --git a/backend/src/main/java/fellowship/mealmaestro/config/exceptions/UserNotFoundException.java b/backend/src/main/java/fellowship/mealmaestro/config/exceptions/UserNotFoundException.java new file mode 100644 index 00000000..8c8aa3d9 --- /dev/null +++ b/backend/src/main/java/fellowship/mealmaestro/config/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package fellowship.mealmaestro.config.exceptions; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java b/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java index ed17d8a7..16414a49 100644 --- a/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java +++ b/backend/src/main/java/fellowship/mealmaestro/controllers/UserController.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import fellowship.mealmaestro.config.exceptions.UserNotFoundException; import fellowship.mealmaestro.models.UpdateUserRequestModel; import fellowship.mealmaestro.models.auth.AuthenticationRequestModel; import fellowship.mealmaestro.models.auth.AuthenticationResponseModel; @@ -32,7 +33,7 @@ public UserController(AuthenticationService authenticationService, UserService u @PostMapping("/findByEmail") public UserModel findByEmail(@RequestBody UserModel user) { - return userService.findByEmail(user.getEmail()).orElseThrow(() -> new RuntimeException("User not found")); + return userService.findByEmail(user.getEmail()).orElseThrow(() -> new UserNotFoundException("User not found")); } @PostMapping("/register") diff --git a/backend/src/main/java/fellowship/mealmaestro/services/UserService.java b/backend/src/main/java/fellowship/mealmaestro/services/UserService.java index b2f14868..a2c78542 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/UserService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/UserService.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Service; +import fellowship.mealmaestro.config.exceptions.UserNotFoundException; import fellowship.mealmaestro.models.UpdateUserRequestModel; import fellowship.mealmaestro.models.neo4j.UserModel; import fellowship.mealmaestro.repositories.neo4j.UserRepository; @@ -28,7 +29,7 @@ public UserModel updateUser(UpdateUserRequestModel user, String token) { String authToken = token.substring(7); String email = jwtService.extractUserEmail(authToken); UserModel userModel = userRepository.findByEmail(email) - .orElseThrow(() -> new RuntimeException("User not found")); + .orElseThrow(() -> new UserNotFoundException("User not found")); userModel.setName(user.getUsername()); return userRepository.save(userModel); @@ -37,6 +38,6 @@ public UserModel updateUser(UpdateUserRequestModel user, String token) { public UserModel getUser(String token) { String authToken = token.substring(7); String email = jwtService.extractUserEmail(authToken); - return userRepository.findByEmail(email).orElseThrow(() -> new RuntimeException("User not found")); + return userRepository.findByEmail(email).orElseThrow(() -> new UserNotFoundException("User not found")); } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/auth/AuthenticationService.java b/backend/src/main/java/fellowship/mealmaestro/services/auth/AuthenticationService.java index a1c8bea4..2f5c49a9 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/auth/AuthenticationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/auth/AuthenticationService.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import fellowship.mealmaestro.config.exceptions.UserNotFoundException; import fellowship.mealmaestro.models.auth.AuthenticationRequestModel; import fellowship.mealmaestro.models.auth.AuthenticationResponseModel; import fellowship.mealmaestro.models.auth.AuthorityRoleModel; @@ -76,7 +77,7 @@ public AuthenticationResponseModel authenticate(AuthenticationRequestModel reque request.getPassword())); var user = userRepository.findByEmail(request.getEmail()) - .orElseThrow(() -> new RuntimeException("User not found")); + .orElseThrow(() -> new UserNotFoundException("User not found")); var jwt = jwtService.generateToken(user); return new AuthenticationResponseModel(jwt); From c7e05b22a647f24c8606d0eee44f00b11e7bed30 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 13:53:52 +0200 Subject: [PATCH 64/97] actually fixed now --- .../mealmaestro/models/neo4j/ViewModel.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index b07d1042..924be042 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -1,6 +1,9 @@ package fellowship.mealmaestro.models.neo4j; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; @@ -13,9 +16,9 @@ public class ViewModel { @GeneratedValue private Long id; - String[] Keys; - Double[] scoreValues; - Double[] nScoreValues; + private String[] Keys; + private List scoreValues; + private List nScoreValues; private Double max =0.0; private Double min =0.0; @@ -88,22 +91,27 @@ public void normalise() { nScoreValues = hashMapValuesToArray(nScoreMap); } - public static Double[] hashMapValuesToArray(HashMap hashMap) { - return hashMap.values().toArray(new Double[0]); + public static List hashMapValuesToArray(HashMap hashMap) { + List listFromHashMap = new ArrayList<>(); + for (Map.Entry entry : hashMap.entrySet()) { + Double value = entry.getValue(); + listFromHashMap.add(value); + } + return listFromHashMap; } public static String[] hashMapKeysToArray(HashMap hashMap) { return hashMap.keySet().toArray(new String[0]); } - public static HashMap arraysToHashMap(String[] keys, Double[] values) { - if (keys.length != values.length) { + public static HashMap arraysToHashMap(String[] keys, List nScoreValues2) { + if (keys.length != nScoreValues2.size()) { throw new IllegalArgumentException("Keys and values arrays must have the same length."); } HashMap hashMap = new HashMap<>(); for (int i = 0; i < keys.length; i++) { - hashMap.put(keys[i], values[i]); + hashMap.put(keys[i], nScoreValues2.get(i)); } return hashMap; } From 10d979ffdb11324fb5237b3d6beee53b5599c1d4 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 13:54:25 +0200 Subject: [PATCH 65/97] value tweaks --- .../fellowship/mealmaestro/services/HydrationService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java index cfbeb164..df828bd3 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/HydrationService.java @@ -73,13 +73,13 @@ private static List parseCommaSeparatedString(String input) { private static Double getScoreValue(String entryType) { switch (entryType.toLowerCase()) { case "regenerated": - return -0.4; + return -0.2; case "like": return 1.0; case "dislike": - return -0.7; + return -0.5; case "save": return 0.7; From a79b84bf8e330392ed646711cc981e801293a101 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 14:40:25 +0200 Subject: [PATCH 66/97] mapped out recommendation --- .../services/RecommendationService.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java index e0ed7af4..a9608986 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java @@ -1,10 +1,38 @@ package fellowship.mealmaestro.services; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; +import fellowship.mealmaestro.models.neo4j.FoodModel; +import fellowship.mealmaestro.models.neo4j.MealModel; +import fellowship.mealmaestro.models.neo4j.PantryModel; +import fellowship.mealmaestro.models.neo4j.ViewModel; import fellowship.mealmaestro.repositories.neo4j.UserRepository; public class RecommendationService { @Autowired - private UserRepository userRepository; + private UserService userService; + @Autowired + private PantryService pantryService; + @Autowired + private MealDatabaseService mealDatabaseService; + @Autowired + private MealManagementService mealManagementService; + + public MealModel getRecommendedMeal(String token){ + MealModel recMealModel = new MealModel(); + //get view and pantry + List pantryModel = pantryService.getPantry(token.substring(7)); + ViewModel viewModel = userService.getUser(token).getView(); + //get positive keys + + // compare view and pantry + + //use list to find db meal + + //query gpt + + return recMealModel; + } } From 60527bb10b5be976319979c9e357158d738534a0 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 14:50:11 +0200 Subject: [PATCH 67/97] function to get certain values added --- .../mealmaestro/models/neo4j/ViewModel.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index 924be042..bba173b8 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -9,6 +9,8 @@ import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; +import kotlin.coroutines.ContinuationInterceptor.Key; + @Node("View") public class ViewModel { @@ -116,5 +118,19 @@ public static HashMap arraysToHashMap(String[] keys, List getPositiveNScores(Double minNValue) throws Exception{ + ListKeys = new ArrayList<>(); + if(this.Keys != null && this.Keys.length > 0){ + HashMap nScoreMap = arraysToHashMap(this.Keys, this.nScoreValues); + + for (Map.Entry entry : nScoreMap.entrySet()) { + if (entry.getValue() >= minNValue) { + Keys.add(entry.getKey()); + } + } + + return Keys; + } + else return null; + } } From 0e6d09e32847ab1df73900fdddb4d0f13b3e5595 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 14:50:27 +0200 Subject: [PATCH 68/97] Update ViewModel.java --- .../java/fellowship/mealmaestro/models/neo4j/ViewModel.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java index bba173b8..e1d927ce 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/ViewModel.java @@ -9,8 +9,6 @@ import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; -import kotlin.coroutines.ContinuationInterceptor.Key; - @Node("View") public class ViewModel { From 0b7463aa337c095d6f590b793672553ed2fabdf5 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 14:54:55 +0200 Subject: [PATCH 69/97] pantry list function --- .../fellowship/mealmaestro/models/neo4j/PantryModel.java | 8 ++++++++ .../mealmaestro/services/RecommendationService.java | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/PantryModel.java b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/PantryModel.java index 0cce95da..5631efb5 100644 --- a/backend/src/main/java/fellowship/mealmaestro/models/neo4j/PantryModel.java +++ b/backend/src/main/java/fellowship/mealmaestro/models/neo4j/PantryModel.java @@ -45,4 +45,12 @@ public String toString() { return csv; } + + public List getNameList() throws Exception{ + List list = new ArrayList<>(); + for (FoodModel foodModel : this.foods) { + list.add(foodModel.getName()); + } + return list; + } } diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java index a9608986..28353e75 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java @@ -20,12 +20,13 @@ public class RecommendationService { @Autowired private MealManagementService mealManagementService; - public MealModel getRecommendedMeal(String token){ + public MealModel getRecommendedMeal(String token) throws Exception{ MealModel recMealModel = new MealModel(); //get view and pantry List pantryModel = pantryService.getPantry(token.substring(7)); ViewModel viewModel = userService.getUser(token).getView(); //get positive keys + List validIngredients = viewModel.getPositiveNScores(-0.1); // compare view and pantry From b473f544669ecc977b1dda81caada37c31fb624e Mon Sep 17 00:00:00 2001 From: Skulderlock <78735770+SkulderLock@users.noreply.github.com> Date: Sat, 23 Sep 2023 15:05:01 +0200 Subject: [PATCH 70/97] =?UTF-8?q?=E2=9C=A8=20qol=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/MealManagementService.java | 3 + .../services/SettingsServiceTest.java | 86 ------------------ .../pages/acc-profile/acc-profile.page.html | 4 +- .../pages/acc-profile/acc-profile.page.scss | 26 ++---- frontend/src/app/pages/login/login.page.html | 91 +++++++++++-------- 5 files changed, 64 insertions(+), 146 deletions(-) delete mode 100644 backend/src/test/java/fellowship/mealmaestro/services/SettingsServiceTest.java diff --git a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java index 2ce340f5..2e789cf0 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/MealManagementService.java @@ -50,6 +50,9 @@ public MealModel generateMeal(String mealType, String token) { String imageUrl = ""; imageUrl = unsplashService.fetchPhoto(mealJson.get("name").asText()); + if (!imageUrl.contains(("https://"))) + imageUrl = "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=640&q=80"; + ObjectNode mealObject = objectMapper.valueToTree(mealJson); mealObject.put("type", mealType); mealObject.put("image", imageUrl); diff --git a/backend/src/test/java/fellowship/mealmaestro/services/SettingsServiceTest.java b/backend/src/test/java/fellowship/mealmaestro/services/SettingsServiceTest.java deleted file mode 100644 index 0a4a027e..00000000 --- a/backend/src/test/java/fellowship/mealmaestro/services/SettingsServiceTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package fellowship.mealmaestro.services; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.boot.test.context.SpringBootTest; - -import fellowship.mealmaestro.models.neo4j.SettingsModel; -import fellowship.mealmaestro.repositories.neo4j.SettingsRepository; -import fellowship.mealmaestro.services.auth.JwtService; - -import java.util.Arrays; - -@SpringBootTest - -public class SettingsServiceTest { - - @InjectMocks - SettingsService settingsService; - - @Mock - JwtService jwtService; - - @Mock - SettingsRepository settingsRepository; - - @BeforeEach - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testGetSettings() { - SettingsModel settingsModel = new SettingsModel(); - settingsModel.setGoal("Lose Weight"); - settingsModel.setShoppingInterval("Weekly"); - settingsModel.setFoodPreferences(Arrays.asList("Vegetarian")); - settingsModel.setCalorieAmount(2000); - settingsModel.setBudgetRange("Low"); - settingsModel.setProtein(40); - settingsModel.setCarbs(40); - settingsModel.setFat(20); - settingsModel.setAllergies(Arrays.asList("Peanuts")); - settingsModel.setCookingTime("30 minutes"); - settingsModel.setUserHeight(180); - settingsModel.setUserWeight(70); - settingsModel.setUserBMI(22); - - when(jwtService.extractUserEmail("validToken")).thenReturn("test@example.com"); - when(settingsService.getSettings("test@example.com")).thenReturn(settingsModel); - - SettingsModel result = settingsService.getSettings("validToken"); - - assertEquals(settingsModel, result); - } - - @Test - public void testUpdateSettings() { - SettingsModel settingsModel = new SettingsModel(); - settingsModel.setGoal("Gain Weight"); - settingsModel.setShoppingInterval("Monthly"); - settingsModel.setFoodPreferences(Arrays.asList("Vegan")); - settingsModel.setCalorieAmount(3000); - settingsModel.setBudgetRange("High"); - settingsModel.setProtein(30); - settingsModel.setCarbs(50); - settingsModel.setFat(20); - settingsModel.setAllergies(Arrays.asList("Dairy")); - settingsModel.setCookingTime("45 minutes"); - settingsModel.setUserHeight(175); - settingsModel.setUserWeight(75); - settingsModel.setUserBMI(24); - - when(jwtService.extractUserEmail("validToken")).thenReturn("test@example.com"); - - settingsService.updateSettings(settingsModel, "validToken"); - - verify(settingsService).updateSettings(settingsModel, "test@example.com"); - } -} diff --git a/frontend/src/app/pages/acc-profile/acc-profile.page.html b/frontend/src/app/pages/acc-profile/acc-profile.page.html index 6f9e50de..046a7066 100644 --- a/frontend/src/app/pages/acc-profile/acc-profile.page.html +++ b/frontend/src/app/pages/acc-profile/acc-profile.page.html @@ -25,9 +25,9 @@ Logout - Delete Account + > --> - + - + - - - - - Please enter a valid email. - - - - - Please enter a password. - - - - - Login + + + + Please enter a valid email. + + + + + Please enter a password. + - - + + + + Login + + + - Forget Password? + - Don't have an account? Sign Up + Don't have an account? Sign Up - - \ No newline at end of file + From 7762403d7726db7c12af6dd1d278316ed5536ed1 Mon Sep 17 00:00:00 2001 From: skitsbi <88lerouxt@gmail.com> Date: Sat, 23 Sep 2023 15:05:23 +0200 Subject: [PATCH 71/97] Common items Function added --- .../services/RecommendationService.java | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java index 28353e75..6bbe903c 100644 --- a/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java +++ b/backend/src/main/java/fellowship/mealmaestro/services/RecommendationService.java @@ -1,5 +1,6 @@ package fellowship.mealmaestro.services; +import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -20,20 +21,42 @@ public class RecommendationService { @Autowired private MealManagementService mealManagementService; + private final Double MIN_VALUE = -0.01; + public MealModel getRecommendedMeal(String token) throws Exception{ MealModel recMealModel = new MealModel(); //get view and pantry - List pantryModel = pantryService.getPantry(token.substring(7)); - ViewModel viewModel = userService.getUser(token).getView(); - //get positive keys - List validIngredients = viewModel.getPositiveNScores(-0.1); - + List pantryItems = userService.getUser(token).getPantry().getNameList(); + List validIngredients = userService.getUser(token).getView().getPositiveNScores(MIN_VALUE); + // compare view and pantry + //use list to find db meal + //query gpt return recMealModel; } + + public static List findCommonItems(List list1, List