diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..863dcaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/webapp/Kochstube/bin/ +/webapp/Kochstube/obj/ diff --git a/DEVY.md b/DEVY.md index dc7ff8c..c0b0834 100644 --- a/DEVY.md +++ b/DEVY.md @@ -4,9 +4,19 @@ Rezepte & Kochpläne sammeln. Fokus auf gesundes, leckeres Essen. Tausi gibt Zutaten vor, ich schlage passende Rezepte vor - bei Bedarf mit Webrecherche. +## Tech Stack + +- **Framework:** .NET 9 (ASP.NET Razor Pages) +- **Port:** 18683 +- **Host:** stein2 (192.168.100.200) +- **Pfad auf stein2:** `/var/lib/docker/workspace/stacks/openclaw/meleki/home/kochstube/` +- **systemd Service:** `kochstube` (enabled, auto-restart) +- **Rebuild nach Code-Änderungen:** `sudo dotnet build -c Release` im webapp/Kochstube Ordner, dann `sudo systemctl restart kochstube` +- **Neue Rezepte:** JSON in `rezepte/` ablegen, wird automatisch geladen (kein Rebuild nötig) + ## Pfad -`/_stacks/openclaw/meleki/workspace/projects/kochstube` +`/_stacks/openclaw/meleki/home/kochstube` (auf stein2, via SSH erreichbar) ## Rezeptquellen (primär) @@ -15,6 +25,5 @@ Rezepte & Kochpläne sammeln. Fokus auf gesundes, leckeres Essen. Tausi gibt Zut ## Notizen -- Kein SSH/stein2-Zugriff nötig - alles direkt im eigenen Workspace schreiben -- Rezepte als JSON in `rezepte/` ablegen (für spätere Website) -- +- Rezepte als JSON in `rezepte/` ablegen +- Web-App cached Rezepte 10 Sekunden diff --git a/rezepte/chinesisches-kartoffel-stir-fry.json b/rezepte/chinesisches-kartoffel-stir-fry.json new file mode 100644 index 0000000..645a6c1 --- /dev/null +++ b/rezepte/chinesisches-kartoffel-stir-fry.json @@ -0,0 +1,38 @@ +{ + "id": "chinesisches-kartoffel-stir-fry", + "titel": "Chinesisches Kartoffel-Stir-Fry (Szechuan-Art)", + "beschreibung": "Knackige Kartoffelstreifen aus dem Wok, sauer-scharf mit Sojasauce, Essig und Paprika. Steht in 15 Minuten auf dem Tisch – chinesische Alltagsküche at its best.", + "bild": "https://www.eat-this.org/wp-content/uploads/2022/09/eat_this_chinesisches_kartoffel-stir-fry-003.jpg", + "kategorie": "Hauptspeise", + "tags": ["asiatisch", "schnell", "pfanne", "vegan", "kartoffeln", "szechuan"], + "schwierigkeit": "einfach", + "zubereitungszeit_min": 15, + "portionen": 2, + "zutaten": [ + {"menge": "650 g", "zutat": "festkochende Kartoffeln"}, + {"menge": "2", "zutat": "Schalotten (oder kleine Zwiebeln)"}, + {"menge": "2 Zehen", "zutat": "Knoblauch"}, + {"menge": "½", "zutat": "grüne Paprika"}, + {"menge": "1 TL", "zutat": "Szechuanpfeffer (oder schwarzer Pfeffer)"}, + {"menge": "2-5", "zutat": "getrocknete Chilis (nach Geschmack)"}, + {"menge": "3 EL", "zutat": "neutrales Öl"}, + {"menge": "½ TL", "zutat": "Salz"}, + {"menge": "3 EL", "zutat": "Balsamico-Essig (ersetzt chin. schwarzen Essig)"}, + {"menge": "1 EL", "zutat": "Sojasauce"} + ], + "zubereitung": [ + "Kartoffeln schälen und in dünne Streifen (3-4 mm) schneiden oder hobeln. Gut abspülen und in kaltem Wasser einlegen.", + "Zwiebeln und Knoblauch in dünne Scheiben, Paprika in Streifen schneiden. Chilis grob zerkleinern.", + "Kartoffeln abgießen und sehr gut abtropfen lassen.", + "Öl in einem Wok oder einer Pfanne stark erhitzen. Chilis, Pfeffer und Knoblauch hineingeben und 20-30 Sekunden rösten.", + "Hitze auf Maximum stellen, Zwiebeln, Kartoffeln und Paprika dazugeben, salzen und unter ständigem Rühren 3-4 Minuten braten, bis die Kartoffeln etwas weicher, aber noch knackig sind.", + "Mit Essig und Sojasauce ablöschen, bei Bedarf nachsalzen und sofort heiß servieren." + ], + "notizen": "Chinesischen schwarzen Essig (Chinkiang) kann man durch Balsamico ersetzen. Kartoffeln müssen festkochend sein! Dazu passt Reis. Chilis weglassen für milde Version.", + "quelle": { + "name": "eat-this.org", + "url": "https://www.eat-this.org/chinesisches-kartoffel-stir-fry/", + "autor": "Jörg" + }, + "erstellt": "2026-02-17" +} diff --git a/rezepte/gebackener-feta-kartoffeln-rahmgurken.json b/rezepte/gebackener-feta-kartoffeln-rahmgurken.json new file mode 100644 index 0000000..3dd3de1 --- /dev/null +++ b/rezepte/gebackener-feta-kartoffeln-rahmgurken.json @@ -0,0 +1,41 @@ +{ + "id": "gebackener-feta-kartoffeln-rahmgurken", + "titel": "Gebackener Feta mit Kartoffeln und Rahmgurken", + "beschreibung": "Knusprig panierter Feta auf Salzkartoffeln mit cremigem Gurkenrahmsalat – ein einfaches, deftiges Gericht mit wenig Aufwand.", + "bild": "https://ais.kochbar.de/kbrezept/384593_410941/1200x1200/gebackener-feta-mit-kartoffeln-und-rahmgurken-rezept.jpg", + "kategorie": "Hauptspeise", + "tags": ["deftig", "schnell", "vegetarisch", "kartoffeln", "feta"], + "schwierigkeit": "einfach", + "zubereitungszeit_min": 35, + "portionen": 2, + "zutaten": [ + {"menge": "750 g", "zutat": "Kartoffeln (festkochend)"}, + {"menge": "2 Stück", "zutat": "Feta"}, + {"menge": "etwas", "zutat": "Semmelbrösel"}, + {"menge": "etwas", "zutat": "Butter"}, + {"menge": "1", "zutat": "Ei"}, + {"menge": "1", "zutat": "Salatgurke"}, + {"menge": "200 ml", "zutat": "Schmand"}, + {"menge": "½", "zutat": "Zwiebel"}, + {"menge": "etwas", "zutat": "Salz"}, + {"menge": "etwas", "zutat": "Pfeffer"}, + {"menge": "etwas", "zutat": "Essig"} + ], + "zubereitung": [ + "Kartoffeln waschen und in kochendem Salzwasser gar kochen.", + "Gurke schälen, fein hobeln und in einer Schüssel salzen. Kurz ziehen lassen.", + "Feta abtropfen lassen, in verquirltem Ei wenden, dann in Semmelbröseln wenden.", + "Butter in einer Pfanne erhitzen und den Feta auf beiden Seiten goldgelb ausbacken.", + "Gurken aus der Schüssel nehmen und die Feuchtigkeit ausdrücken.", + "Zwiebel schälen und fein würfeln. Schmand und Zwiebeln zu den Gurken geben.", + "Gurkensalat mit Essig, Salz und Pfeffer abschmecken.", + "Kartoffeln abgießen, pellen und mit gebackenem Feta und Rahmgurken servieren." + ], + "notizen": "Schmand muss evtl. besorgt werden. Alternativ Saure Sahne oder Crème fraîche verwenden. Semmelbrösel können aus altem Brot selbst gemacht werden.", + "quelle": { + "name": "kochbar.de", + "url": "https://www.kochbar.de/rezept/384593/Gebackener-Feta-mit-Kartoffeln-und-Rahmgurken.html", + "autor": "kochbar-User" + }, + "erstellt": "2026-02-17" +} diff --git a/rezepte/pad-thai-gruener-spargel-tofu.json b/rezepte/pad-thai-gruener-spargel-tofu.json new file mode 100755 index 0000000..bf526e6 --- /dev/null +++ b/rezepte/pad-thai-gruener-spargel-tofu.json @@ -0,0 +1,45 @@ +{ + "id": "pad-thai-gruener-spargel-tofu", + "titel": "Pad Thai mit grünem Spargel und Tofu", + "beschreibung": "Authentisches Pad Thai mit knackigem grünem Spargel, kross gebratenem Tofu und würziger Sauce - in 30 Minuten fertig.", + "bild": "https://www.eat-this.org/wp-content/uploads/2017/05/Pad-Thai-mit-gruenem-Spargel-1.jpg", + "kategorie": "Hauptspeise", + "tags": ["vegan", "schnell", "asiatisch", "nudeln", "thai"], + "schwierigkeit": "einfach", + "zubereitungszeit_min": 30, + "portionen": 4, + "zutaten": [ + {"menge": "400 g", "zutat": "dünne Reisnudeln"}, + {"menge": "400 g", "zutat": "grüner Spargel"}, + {"menge": "1 Bund", "zutat": "Frühlingszwiebeln"}, + {"menge": "4 Zehen", "zutat": "Knoblauch"}, + {"menge": "1 Stück", "zutat": "Ingwer (daumengroß)"}, + {"menge": "75 ml", "zutat": "Sojasauce"}, + {"menge": "4 EL", "zutat": "Tafelessig"}, + {"menge": "2 EL", "zutat": "Rohrohrzucker"}, + {"menge": "4 EL", "zutat": "Erdnüsse"}, + {"menge": "300 g", "zutat": "Tofu"}, + {"menge": "4 EL", "zutat": "Erdnussöl"}, + {"menge": "250 g", "zutat": "Mungbohnensprossen"}, + {"menge": "0.5 Bund", "zutat": "Koriander"}, + {"menge": "2", "zutat": "rote Chili"} + ], + "zubereitung": [ + "Reisnudeln in einer Schüssel mit kochendem Wasser übergießen, abdecken und beiseite stellen.", + "Spargel diagonal in 1 cm dicke Scheiben, den weißen Teil der Frühlingszwiebeln in 4 cm lange Stücke schneiden. Grünen Teil fein hacken und für die Deko beiseite stellen.", + "Knoblauch pressen, Ingwer reiben. Mit Sojasauce, Essig, Zucker und 8 EL Wasser verrühren. Sauce beiseite stellen.", + "Erdnüsse in einem Wok ohne Öl bei mittlerer bis hoher Hitze 2 Minuten unter dauerndem Rühren rösten. Aus der Pfanne nehmen.", + "Tofu in 3 cm große Würfel schneiden. Öl in den Wok geben, Tofuwürfel bei mittlerer bis hoher Hitze 6 Minuten goldbraun braten.", + "Frühlingszwiebeln dazu geben, weitere 2 Minuten braten.", + "Spargel unterheben, 3 Minuten unter häufigem Rühren rösten.", + "Nudeln abgießen, unter das Gemüse mischen und 1 Minute pfannenrühren. Mit Sauce ablöschen, Sprossen unterrühren und 2 Minuten schmoren.", + "Mit Erdnüssen, frisch gehacktem Koriander und Chili toppen." + ], + "notizen": null, + "quelle": { + "name": "eat-this.org", + "url": "https://www.eat-this.org/pad-thai-mit-gruenem-spargel-und-tofu/", + "autor": null + }, + "erstellt": "2026-02-17" +} diff --git a/rezepte/shakshuka-mit-feta.json b/rezepte/shakshuka-mit-feta.json new file mode 100644 index 0000000..adec590 --- /dev/null +++ b/rezepte/shakshuka-mit-feta.json @@ -0,0 +1,92 @@ +{ + "id": "shakshuka-mit-feta", + "titel": "Shakshuka mit Feta", + "beschreibung": "Würzige nordafrikanische Eierpfanne in aromatischer Tomaten-Paprika-Sauce, getoppt mit cremigem Feta. Perfekt zum Dippen mit frischem Brot.", + "bild": "", + "kategorie": "Hauptspeise", + "tags": [ + "pfanne", + "vegetarisch", + "deftig", + "orientalisch", + "eier" + ], + "schwierigkeit": "einfach", + "zubereitungszeit_min": 30, + "portionen": 2, + "zutaten": [ + { + "menge": "1", + "zutat": "Zwiebel" + }, + { + "menge": "2 Zehen", + "zutat": "Knoblauch" + }, + { + "menge": "1 große", + "zutat": "Paprika (rot)" + }, + { + "menge": "1 kleine", + "zutat": "Zucchini" + }, + { + "menge": "2 EL", + "zutat": "Olivenöl" + }, + { + "menge": "1 EL", + "zutat": "Tomatenmark" + }, + { + "menge": "400 ml", + "zutat": "Passierte Tomaten" + }, + { + "menge": "1 TL", + "zutat": "Kreuzkümmel (Cumin)" + }, + { + "menge": "1 TL", + "zutat": "Paprikapulver (geräuchert)" + }, + { + "menge": "0.5 TL", + "zutat": "Chiliflocken" + }, + { + "menge": "1 TL", + "zutat": "Zucker" + }, + { + "menge": "", + "zutat": "Salz und Pfeffer" + }, + { + "menge": "4", + "zutat": "Eier" + }, + { + "menge": "80 g", + "zutat": "Feta" + } + ], + "zubereitung": [ + "Zwiebel und Knoblauch fein hacken. Paprika in kleine Würfel schneiden, Zucchini in Halbmonde.", + "Olivenöl in einer großen Pfanne mit Deckel bei mittlerer Hitze erhitzen. Zwiebel 3-4 Minuten glasig dünsten.", + "Paprika, Zucchini und Knoblauch dazu geben und 3-4 Minuten mitbraten.", + "Tomatenmark einrühren und 1 Minute anrösten. Kreuzkümmel, Paprikapulver und Chiliflocken dazu und kurz mitrösten.", + "Passierte Tomaten angießen, Zucker, Salz und Pfeffer zugeben. Alles 8-10 Minuten bei mittlerer Hitze köcheln lassen, bis die Sauce eingedickt ist.", + "Mit einem Löffel 4 Mulden in die Sauce drücken und je ein Ei hineinschlagen. Feta darüber bröseln.", + "Deckel auflegen und bei niedriger Hitze 6-8 Minuten garen, bis das Eiweiß gestockt ist, das Eigelb aber noch weich.", + "Mit frischem Brot servieren." + ], + "notizen": "Brot muss ggf. besorgt werden (Fladenbrot ideal). Eigelb soll noch flüssig sein — lieber kürzer garen und nachschauen. Wer es schärfer mag, mehr Chiliflocken nehmen.", + "quelle": { + "name": "Klassisches Rezept", + "url": "", + "autor": "Meleki" + }, + "erstellt": "2026-02-17" +} diff --git a/webapp/Kochstube/Kochstube.csproj b/webapp/Kochstube/Kochstube.csproj new file mode 100644 index 0000000..0c27b6c --- /dev/null +++ b/webapp/Kochstube/Kochstube.csproj @@ -0,0 +1,7 @@ + + + net9.0 + enable + enable + + diff --git a/webapp/Kochstube/Models/Rezept.cs b/webapp/Kochstube/Models/Rezept.cs new file mode 100644 index 0000000..dba22eb --- /dev/null +++ b/webapp/Kochstube/Models/Rezept.cs @@ -0,0 +1,69 @@ +using System.Text.Json.Serialization; + +namespace Kochstube.Models; + +public class Rezept +{ + [JsonPropertyName("id")] + public string Id { get; set; } = ""; + + [JsonPropertyName("titel")] + public string Titel { get; set; } = ""; + + [JsonPropertyName("beschreibung")] + public string Beschreibung { get; set; } = ""; + + [JsonPropertyName("bild")] + public string? Bild { get; set; } + + [JsonPropertyName("kategorie")] + public string Kategorie { get; set; } = ""; + + [JsonPropertyName("tags")] + public List Tags { get; set; } = []; + + [JsonPropertyName("schwierigkeit")] + public string Schwierigkeit { get; set; } = ""; + + [JsonPropertyName("zubereitungszeit_min")] + public int ZubereitungszeitMin { get; set; } + + [JsonPropertyName("portionen")] + public int Portionen { get; set; } + + [JsonPropertyName("zutaten")] + public List Zutaten { get; set; } = []; + + [JsonPropertyName("zubereitung")] + public List Zubereitung { get; set; } = []; + + [JsonPropertyName("notizen")] + public string? Notizen { get; set; } + + [JsonPropertyName("quelle")] + public Quelle? Quelle { get; set; } + + [JsonPropertyName("erstellt")] + public string? Erstellt { get; set; } +} + +public class Zutat +{ + [JsonPropertyName("menge")] + public string Menge { get; set; } = ""; + + [JsonPropertyName("zutat")] + public string Name { get; set; } = ""; +} + +public class Quelle +{ + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("url")] + public string? Url { get; set; } + + [JsonPropertyName("autor")] + public string? Autor { get; set; } +} diff --git a/webapp/Kochstube/Pages/Index.cshtml b/webapp/Kochstube/Pages/Index.cshtml new file mode 100644 index 0000000..53f4bf2 --- /dev/null +++ b/webapp/Kochstube/Pages/Index.cshtml @@ -0,0 +1,62 @@ +@page +@model IndexModel + +@section FilterBar { +
+ +
+} + +
@Model.Rezepte.Count Rezept(e)
+ + diff --git a/webapp/Kochstube/Pages/Index.cshtml.cs b/webapp/Kochstube/Pages/Index.cshtml.cs new file mode 100644 index 0000000..77af01c --- /dev/null +++ b/webapp/Kochstube/Pages/Index.cshtml.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Kochstube.Models; +using Kochstube.Services; + +namespace Kochstube.Pages; + +public class IndexModel : PageModel +{ + private readonly RezeptService _service; + + public IndexModel(RezeptService service) => _service = service; + + public List Rezepte { get; set; } = []; + public List AlleTags { get; set; } = []; + public List AlleKategorien { get; set; } = []; + + [BindProperty(SupportsGet = true)] + public string? Q { get; set; } + + [BindProperty(SupportsGet = true)] + public string? Tag { get; set; } + + [BindProperty(SupportsGet = true)] + public string? Kategorie { get; set; } + + public void OnGet() + { + AlleTags = _service.GetAllTags(); + AlleKategorien = _service.GetAllKategorien(); + Rezepte = _service.Search(Q, Tag, Kategorie); + ViewData["Title"] = "Rezepte"; + } +} diff --git a/webapp/Kochstube/Pages/Rezept.cshtml b/webapp/Kochstube/Pages/Rezept.cshtml new file mode 100644 index 0000000..9592e88 --- /dev/null +++ b/webapp/Kochstube/Pages/Rezept.cshtml @@ -0,0 +1,86 @@ +@page "/rezept/{id}" +@model RezeptModel + +@if (Model.Rezept == null) { return; } + +@{ var r = Model.Rezept; } + +
+ ← Zurück zur Übersicht + +

@r.Titel

+

@r.Beschreibung

+ + @if (!string.IsNullOrEmpty(r.Bild)) + { + @r.Titel + } + +
+
+ Zubereitungszeit + @r.ZubereitungszeitMin min +
+
+ Portionen + @r.Portionen +
+
+ Schwierigkeit + @r.Schwierigkeit +
+
+ Kategorie + @r.Kategorie +
+
+ +
+ @foreach (var tag in r.Tags) + { + @tag + } +
+ +
+

Zutaten

+
    + @foreach (var z in r.Zutaten) + { +
  • + @z.Name + @z.Menge +
  • + } +
+
+ +
+

Zubereitung

+
    + @foreach (var step in r.Zubereitung) + { +
  1. @step
  2. + } +
+
+ + @if (!string.IsNullOrEmpty(r.Notizen)) + { +
+

Notizen

+
@r.Notizen
+
+ } + + @if (r.Quelle != null) + { +
+ Quelle: @r.Quelle.Name + @if (!string.IsNullOrEmpty(r.Quelle.Autor)) + { + von @r.Quelle.Autor + } +
+ } +
diff --git a/webapp/Kochstube/Pages/Rezept.cshtml.cs b/webapp/Kochstube/Pages/Rezept.cshtml.cs new file mode 100644 index 0000000..ad77225 --- /dev/null +++ b/webapp/Kochstube/Pages/Rezept.cshtml.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Kochstube.Models; +using Kochstube.Services; + +namespace Kochstube.Pages; + +public class RezeptModel : PageModel +{ + private readonly RezeptService _service; + + public RezeptModel(RezeptService service) => _service = service; + + public Rezept? Rezept { get; set; } + + public IActionResult OnGet(string id) + { + Rezept = _service.GetById(id); + if (Rezept == null) return NotFound(); + ViewData["Title"] = Rezept.Titel; + return Page(); + } +} diff --git a/webapp/Kochstube/Pages/Shared/_Layout.cshtml b/webapp/Kochstube/Pages/Shared/_Layout.cshtml new file mode 100644 index 0000000..c147538 --- /dev/null +++ b/webapp/Kochstube/Pages/Shared/_Layout.cshtml @@ -0,0 +1,334 @@ + + + + + + @ViewData["Title"] - Kochstube + + + +
+ +

🍳 Kochstube

+

Rezepte von Tausi & Nocki

+
+
+ @RenderSection("FilterBar", required: false) +
+ @RenderBody() +
+ + diff --git a/webapp/Kochstube/Pages/_ViewImports.cshtml b/webapp/Kochstube/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..a8d12d0 --- /dev/null +++ b/webapp/Kochstube/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using Kochstube.Models +@namespace Kochstube.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/webapp/Kochstube/Pages/_ViewStart.cshtml b/webapp/Kochstube/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..6791b5b --- /dev/null +++ b/webapp/Kochstube/Pages/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "/Pages/Shared/_Layout.cshtml"; +} diff --git a/webapp/Kochstube/Program.cs b/webapp/Kochstube/Program.cs new file mode 100644 index 0000000..99a7ec2 --- /dev/null +++ b/webapp/Kochstube/Program.cs @@ -0,0 +1,12 @@ +using Kochstube.Services; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddRazorPages(); +builder.Services.AddSingleton(); + +var app = builder.Build(); + +app.UseStaticFiles(); +app.MapRazorPages(); + +app.Run(); diff --git a/webapp/Kochstube/Properties/launchSettings.json b/webapp/Kochstube/Properties/launchSettings.json new file mode 100644 index 0000000..7e592a0 --- /dev/null +++ b/webapp/Kochstube/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5134", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/webapp/Kochstube/Services/RezeptService.cs b/webapp/Kochstube/Services/RezeptService.cs new file mode 100644 index 0000000..319e3ee --- /dev/null +++ b/webapp/Kochstube/Services/RezeptService.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using Kochstube.Models; + +namespace Kochstube.Services; + +public class RezeptService +{ + private readonly string _rezeptPath; + private List? _cache; + private DateTime _lastLoad; + + public RezeptService(IConfiguration config) + { + _rezeptPath = config["RezeptPath"] ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "rezepte"); + } + + public List GetAll() + { + // Reload if older than 10 seconds (good enough for dev) + if (_cache == null || DateTime.UtcNow - _lastLoad > TimeSpan.FromSeconds(10)) + { + _cache = LoadFromDisk(); + _lastLoad = DateTime.UtcNow; + } + return _cache; + } + + public Rezept? GetById(string id) => GetAll().FirstOrDefault(r => r.Id == id); + + public List GetAllTags() => GetAll().SelectMany(r => r.Tags).Distinct().OrderBy(t => t).ToList(); + + public List GetAllKategorien() => GetAll().Select(r => r.Kategorie).Distinct().OrderBy(k => k).ToList(); + + public List Search(string? query, string? tag, string? kategorie) + { + var results = GetAll().AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(query)) + { + var q = query.ToLowerInvariant(); + results = results.Where(r => + r.Titel.Contains(q, StringComparison.OrdinalIgnoreCase) || + r.Beschreibung.Contains(q, StringComparison.OrdinalIgnoreCase) || + r.Zutaten.Any(z => z.Name.Contains(q, StringComparison.OrdinalIgnoreCase))); + } + + if (!string.IsNullOrWhiteSpace(tag)) + results = results.Where(r => r.Tags.Contains(tag, StringComparer.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(kategorie)) + results = results.Where(r => r.Kategorie.Equals(kategorie, StringComparison.OrdinalIgnoreCase)); + + return results.ToList(); + } + + private List LoadFromDisk() + { + var dir = Path.GetFullPath(_rezeptPath); + if (!Directory.Exists(dir)) return []; + + var rezepte = new List(); + foreach (var file in Directory.GetFiles(dir, "*.json")) + { + try + { + var json = File.ReadAllText(file); + var r = JsonSerializer.Deserialize(json); + if (r != null) rezepte.Add(r); + } + catch { /* skip broken files */ } + } + return rezepte.OrderByDescending(r => r.Erstellt ?? "").ThenBy(r => r.Titel).ToList(); + } +} diff --git a/webapp/Kochstube/appsettings.Development.json b/webapp/Kochstube/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/webapp/Kochstube/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/webapp/Kochstube/appsettings.json b/webapp/Kochstube/appsettings.json new file mode 100644 index 0000000..29a17a9 --- /dev/null +++ b/webapp/Kochstube/appsettings.json @@ -0,0 +1,9 @@ +{ + "RezeptPath": "/var/lib/docker/workspace/stacks/openclaw/meleki/home/kochstube/rezepte", + "Urls": "http://0.0.0.0:18683", + "Logging": { + "LogLevel": { + "Default": "Information" + } + } +}