From f0e0f57e7cf3df318e0789c14da4d62c32ebb3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Mon, 9 Feb 2026 18:54:06 +0100 Subject: [PATCH 1/3] feat(web): multiple feature --- backend/Dockerfile | 2 +- backend/build.gradle | 13 +++++++ .../printcalculator/config/CorsConfig.java | 20 ++++++++++ .../controller/QuoteController.java | 29 ++++++++++----- .../printcalculator/service/GCodeParser.java | 14 ++++--- .../service/ProfileManager.java | 29 ++++++++++++++- .../service/QuoteCalculator.java | 4 +- .../service/SlicerService.java | 10 +++-- frontend/angular.json | 14 +++++++ frontend/src/app/app.routes.ts | 4 ++ .../src/app/core/layout/footer.component.html | 4 +- .../src/app/features/legal/legal.routes.ts | 12 ++++++ .../legal/privacy/privacy.component.html | 17 +++++++++ .../legal/privacy/privacy.component.scss | 37 +++++++++++++++++++ .../legal/privacy/privacy.component.ts | 11 ++++++ .../features/legal/terms/terms.component.html | 18 +++++++++ .../features/legal/terms/terms.component.scss | 37 +++++++++++++++++++ .../features/legal/terms/terms.component.ts | 11 ++++++ frontend/src/assets/i18n/en.json | 15 ++++++++ frontend/src/assets/i18n/it.json | 15 ++++++++ 20 files changed, 293 insertions(+), 23 deletions(-) create mode 100644 backend/src/main/java/com/printcalculator/config/CorsConfig.java create mode 100644 frontend/src/app/features/legal/legal.routes.ts create mode 100644 frontend/src/app/features/legal/privacy/privacy.component.html create mode 100644 frontend/src/app/features/legal/privacy/privacy.component.scss create mode 100644 frontend/src/app/features/legal/privacy/privacy.component.ts create mode 100644 frontend/src/app/features/legal/terms/terms.component.html create mode 100644 frontend/src/app/features/legal/terms/terms.component.scss create mode 100644 frontend/src/app/features/legal/terms/terms.component.ts diff --git a/backend/Dockerfile b/backend/Dockerfile index 7d08ed4..32b5ac1 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,7 +19,7 @@ RUN apt-get update && apt-get install -y \ libglib2.0-0 \ libgtk-3-0 \ libdbus-1-3 \ - libwebkit2gtk-4.1-0 \ + libwebkit2gtk-4.0-37 \ && rm -rf /var/lib/apt/lists/* # Install OrcaSlicer diff --git a/backend/build.gradle b/backend/build.gradle index b664e88..82927d9 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'application' id 'org.springframework.boot' version '3.4.1' id 'io.spring.dependency-management' version '1.1.7' } @@ -13,6 +14,10 @@ java { } } +application { + mainClass = 'com.printcalculator.BackendApplication' +} + repositories { mavenCentral() } @@ -27,3 +32,11 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +tasks.named('bootRun') { + args = ["--spring.profiles.active=local"] +} + +application { + applicationDefaultJvmArgs = ["-Dspring.profiles.active=local"] +} diff --git a/backend/src/main/java/com/printcalculator/config/CorsConfig.java b/backend/src/main/java/com/printcalculator/config/CorsConfig.java new file mode 100644 index 0000000..39eaea5 --- /dev/null +++ b/backend/src/main/java/com/printcalculator/config/CorsConfig.java @@ -0,0 +1,20 @@ +package com.printcalculator.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@Profile("local") +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(false); + } +} diff --git a/backend/src/main/java/com/printcalculator/controller/QuoteController.java b/backend/src/main/java/com/printcalculator/controller/QuoteController.java index 301f01a..b85a6a6 100644 --- a/backend/src/main/java/com/printcalculator/controller/QuoteController.java +++ b/backend/src/main/java/com/printcalculator/controller/QuoteController.java @@ -13,16 +13,15 @@ import java.nio.file.Files; import java.nio.file.Path; @RestController -@CrossOrigin(origins = "*") // Allow all for development public class QuoteController { private final SlicerService slicerService; private final QuoteCalculator quoteCalculator; - // Defaults - private static final String DEFAULT_MACHINE = "Bambu_Lab_A1_machine"; - private static final String DEFAULT_FILAMENT = "Bambu_PLA_Basic"; - private static final String DEFAULT_PROCESS = "Bambu_Process_0.20_Standard"; + // Defaults (using aliases defined in ProfileManager) + private static final String DEFAULT_MACHINE = "bambu_a1"; + private static final String DEFAULT_FILAMENT = "pla_basic"; + private static final String DEFAULT_PROCESS = "standard"; public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator) { this.slicerService = slicerService; @@ -32,12 +31,24 @@ public class QuoteController { @PostMapping("/api/quote") public ResponseEntity calculateQuote( @RequestParam("file") MultipartFile file, - @RequestParam(value = "machine", defaultValue = DEFAULT_MACHINE) String machine, - @RequestParam(value = "filament", defaultValue = DEFAULT_FILAMENT) String filament, - @RequestParam(value = "process", defaultValue = DEFAULT_PROCESS) String process + @RequestParam(value = "machine", required = false, defaultValue = DEFAULT_MACHINE) String machine, + @RequestParam(value = "filament", required = false, defaultValue = DEFAULT_FILAMENT) String filament, + @RequestParam(value = "process", required = false) String process, + @RequestParam(value = "quality", required = false) String quality ) throws IOException { - return processRequest(file, machine, filament, process); + // Frontend sends 'quality', backend expects 'process'. + // If process is missing, try quality. If both missing, use default. + String actualProcess = process; + if (actualProcess == null || actualProcess.isEmpty()) { + if (quality != null && !quality.isEmpty()) { + actualProcess = quality; + } else { + actualProcess = DEFAULT_PROCESS; + } + } + + return processRequest(file, machine, filament, actualProcess); } @PostMapping("/calculate/stl") diff --git a/backend/src/main/java/com/printcalculator/service/GCodeParser.java b/backend/src/main/java/com/printcalculator/service/GCodeParser.java index fef2500..f8a1691 100644 --- a/backend/src/main/java/com/printcalculator/service/GCodeParser.java +++ b/backend/src/main/java/com/printcalculator/service/GCodeParser.java @@ -13,9 +13,13 @@ import java.util.regex.Pattern; @Service public class GCodeParser { - private static final Pattern TIME_PATTERN = Pattern.compile("estimated printing time = (.*)"); - private static final Pattern FILAMENT_G_PATTERN = Pattern.compile("filament used \\[g\\] = (.*)"); - private static final Pattern FILAMENT_MM_PATTERN = Pattern.compile("filament used \\[mm\\] = (.*)"); + // OrcaSlicer/BambuStudio format + // ; estimated printing time = 1h 2m 3s + // ; filament used [g] = 12.34 + // ; filament used [mm] = 1234.56 + private static final Pattern TIME_PATTERN = Pattern.compile(";\\s*estimated printing time\\s*=\\s*(.*)"); + private static final Pattern FILAMENT_G_PATTERN = Pattern.compile(";\\s*filament used \\[g\\]\\s*=\\s*(.*)"); + private static final Pattern FILAMENT_MM_PATTERN = Pattern.compile(";\\s*filament used \\[mm\\]\\s*=\\s*(.*)"); public PrintStats parse(File gcodeFile) throws IOException { long seconds = 0; @@ -25,9 +29,9 @@ public class GCodeParser { try (BufferedReader reader = new BufferedReader(new FileReader(gcodeFile))) { String line; - // Scan first 500 lines for efficiency + // Scan first 5000 lines for efficiency (metadata might be further down) int count = 0; - while ((line = reader.readLine()) != null && count < 500) { + while ((line = reader.readLine()) != null && count < 5000) { line = line.trim(); if (!line.startsWith(";")) { count++; diff --git a/backend/src/main/java/com/printcalculator/service/ProfileManager.java b/backend/src/main/java/com/printcalculator/service/ProfileManager.java index 748f5ab..c6bd78a 100644 --- a/backend/src/main/java/com/printcalculator/service/ProfileManager.java +++ b/backend/src/main/java/com/printcalculator/service/ProfileManager.java @@ -14,6 +14,8 @@ import java.util.Iterator; import java.util.Optional; import java.util.logging.Logger; import java.util.stream.Stream; +import java.util.Map; +import java.util.HashMap; @Service public class ProfileManager { @@ -22,9 +24,31 @@ public class ProfileManager { private final String profilesRoot; private final ObjectMapper mapper; + private final Map profileAliases; + public ProfileManager(@Value("${profiles.root:profiles}") String profilesRoot, ObjectMapper mapper) { this.profilesRoot = profilesRoot; this.mapper = mapper; + this.profileAliases = new HashMap<>(); + initializeAliases(); + } + + private void initializeAliases() { + // Machine Aliases + profileAliases.put("bambu_a1", "Bambu Lab A1 0.4 nozzle"); + + // Material Aliases + profileAliases.put("pla_basic", "Bambu PLA Basic @BBL A1"); + profileAliases.put("petg_basic", "Bambu PETG Basic @BBL A1"); + profileAliases.put("tpu_95a", "Bambu TPU 95A @BBL A1"); + + // Quality/Process Aliases + profileAliases.put("draft", "0.24mm Draft @BBL A1"); + profileAliases.put("standard", "0.20mm Standard @BBL A1"); // or 0.20mm Standard @BBL A1 + profileAliases.put("extra_fine", "0.08mm High Quality @BBL A1"); + + // Additional aliases from error logs + profileAliases.put("Bambu_Process_0.20_Standard", "0.20mm Standard @BBL A1"); } public ObjectNode getMergedProfile(String profileName, String type) throws IOException { @@ -36,9 +60,12 @@ public class ProfileManager { } private Path findProfileFile(String name, String type) { + // Check aliases first + String resolvedName = profileAliases.getOrDefault(name, name); + // Simple search: look for name.json in the profiles_root recursively // Type could be "machine", "process", "filament" to narrow down, but for now global search - String filename = name.endsWith(".json") ? name : name + ".json"; + String filename = resolvedName.endsWith(".json") ? resolvedName : resolvedName + ".json"; try (Stream stream = Files.walk(Paths.get(profilesRoot))) { Optional found = stream diff --git a/backend/src/main/java/com/printcalculator/service/QuoteCalculator.java b/backend/src/main/java/com/printcalculator/service/QuoteCalculator.java index b7d8f2e..ada3fe5 100644 --- a/backend/src/main/java/com/printcalculator/service/QuoteCalculator.java +++ b/backend/src/main/java/com/printcalculator/service/QuoteCalculator.java @@ -52,8 +52,8 @@ public class QuoteCalculator { ); List notes = new ArrayList<>(); - notes.add("Generated via Dynamic Slicer (Java Backend)"); + // notes.add("Generated via Dynamic Slicer (Java Backend)"); - return new QuoteResult(totalPrice, "EUR", stats, breakdown, notes); + return new QuoteResult(totalPrice, "CHF", stats, breakdown, notes); } } diff --git a/backend/src/main/java/com/printcalculator/service/SlicerService.java b/backend/src/main/java/com/printcalculator/service/SlicerService.java index 65de876..1594d32 100644 --- a/backend/src/main/java/com/printcalculator/service/SlicerService.java +++ b/backend/src/main/java/com/printcalculator/service/SlicerService.java @@ -55,12 +55,16 @@ public class SlicerService { // 3. Build Command // --load-settings "machine.json;process.json" --load-filaments "filament.json" - String settingsArg = mFile.getAbsolutePath() + ";" + pFile.getAbsolutePath(); - List command = new ArrayList<>(); command.add(slicerPath); + + // Load machine settings command.add("--load-settings"); - command.add(settingsArg); + command.add(mFile.getAbsolutePath()); + + // Load process settings + command.add("--load-settings"); + command.add(pFile.getAbsolutePath()); command.add("--load-filaments"); command.add(fFile.getAbsolutePath()); command.add("--ensure-on-bed"); diff --git a/frontend/angular.json b/frontend/angular.json index 03d92e6..bc7951c 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -70,6 +70,17 @@ "optimization": false, "extractLicenses": false, "sourceMap": true + }, + "local": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.local.ts" + } + ], + "optimization": false, + "extractLicenses": false, + "sourceMap": true } }, @@ -83,6 +94,9 @@ }, "development": { "buildTarget": "frontend:build:development" + }, + "local": { + "buildTarget": "frontend:build:local" } }, "defaultConfiguration": "development" diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 19d0cdf..9d013b7 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -24,6 +24,10 @@ export const routes: Routes = [ { path: 'contact', loadChildren: () => import('./features/contact/contact.routes').then(m => m.CONTACT_ROUTES) + }, + { + path: '', + loadChildren: () => import('./features/legal/legal.routes').then(m => m.LEGAL_ROUTES) } ] } diff --git a/frontend/src/app/core/layout/footer.component.html b/frontend/src/app/core/layout/footer.component.html index 64f9312..d828dcd 100644 --- a/frontend/src/app/core/layout/footer.component.html +++ b/frontend/src/app/core/layout/footer.component.html @@ -4,11 +4,11 @@ 3D fab - + @@ -19,6 +20,7 @@ formControlName="surname" label="USER_DETAILS.SURNAME" placeholder="USER_DETAILS.SURNAME_PLACEHOLDER" + [required]="true" [error]="form.get('surname')?.invalid && form.get('surname')?.touched ? ('COMMON.REQUIRED' | translate) : null"> @@ -32,6 +34,7 @@ label="USER_DETAILS.EMAIL" type="email" placeholder="USER_DETAILS.EMAIL_PLACEHOLDER" + [required]="true" [error]="form.get('email')?.invalid && form.get('email')?.touched ? ('COMMON.INVALID_EMAIL' | translate) : null"> @@ -41,6 +44,7 @@ label="USER_DETAILS.PHONE" type="tel" placeholder="USER_DETAILS.PHONE_PLACEHOLDER" + [required]="true" [error]="form.get('phone')?.invalid && form.get('phone')?.touched ? ('COMMON.REQUIRED' | translate) : null"> @@ -51,6 +55,7 @@ formControlName="address" label="USER_DETAILS.ADDRESS" placeholder="USER_DETAILS.ADDRESS_PLACEHOLDER" + [required]="true" [error]="form.get('address')?.invalid && form.get('address')?.touched ? ('COMMON.REQUIRED' | translate) : null"> @@ -61,6 +66,7 @@ formControlName="zip" label="USER_DETAILS.ZIP" placeholder="USER_DETAILS.ZIP_PLACEHOLDER" + [required]="true" [error]="form.get('zip')?.invalid && form.get('zip')?.touched ? ('COMMON.REQUIRED' | translate) : null"> @@ -69,6 +75,7 @@ formControlName="city" label="USER_DETAILS.CITY" placeholder="USER_DETAILS.CITY_PLACEHOLDER" + [required]="true" [error]="form.get('city')?.invalid && form.get('city')?.touched ? ('COMMON.REQUIRED' | translate) : null"> diff --git a/frontend/src/app/shared/components/app-input/app-input.component.html b/frontend/src/app/shared/components/app-input/app-input.component.html index 7e30cf2..6ec048e 100644 --- a/frontend/src/app/shared/components/app-input/app-input.component.html +++ b/frontend/src/app/shared/components/app-input/app-input.component.html @@ -1,5 +1,10 @@
- @if (label()) { } + @if (label()) { + + } ('text'); placeholder = input(''); error = input(null); + required = input(false); value: string = ''; disabled = false; -- 2.49.1 From 3b4ef37e58a83320791ed8def684c3beb8f637a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Mon, 9 Feb 2026 19:29:14 +0100 Subject: [PATCH 3/3] feat(web): * for reaquired field --- backend/build.gradle | 2 ++ .../src/main/resources/application.properties | 8 ++++++++ docker-compose.deploy.yml | 4 ++-- docker-compose.yml | 17 +++++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index 82927d9..35b1154 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -24,6 +24,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'org.postgresql:postgresql' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index f9efc6e..49c9036 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,6 +1,14 @@ spring.application.name=backend server.port=8000 +# Database Configuration +spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/printcalc} +spring.datasource.username=${DB_USERNAME:printcalc} +spring.datasource.password=${DB_PASSWORD:printcalc_secret} +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect + + # Slicer Configuration # Map SLICER_PATH env var if present (default to /opt/orcaslicer/AppRun or Mac path) slicer.path=${SLICER_PATH:/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer} diff --git a/docker-compose.deploy.yml b/docker-compose.deploy.yml index 14cdd1a..94bdfb2 100644 --- a/docker-compose.deploy.yml +++ b/docker-compose.deploy.yml @@ -15,7 +15,7 @@ services: - MARKUP_PERCENT=${MARKUP_PERCENT} - TEMP_DIR=/app/temp - PROFILES_DIR=/app/profiles - restart: unless-stopped + restart: always volumes: - backend_profiles_${ENV}:/app/profiles @@ -26,7 +26,7 @@ services: - "${FRONTEND_PORT}:8008" depends_on: - backend - restart: unless-stopped + restart: always volumes: backend_profiles_prod: diff --git a/docker-compose.yml b/docker-compose.yml index b62f814..7009b84 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,4 +25,21 @@ services: - "80:80" depends_on: - backend + - db restart: unless-stopped + + db: + image: postgres:15-alpine + container_name: print-calculator-db + environment: + - POSTGRES_USER=printcalc + - POSTGRES_PASSWORD=printcalc_secret + - POSTGRES_DB=printcalc + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + postgres_data: -- 2.49.1