Add LOT API v1 endpoint with full batch details
Deploy / deploy (push) Successful in 15s

Route changed to /api/v1/{lot_number}. LotResource now includes
packing details, units_produced, quantity_g and recipe version.
CORS config added for public API access. README documents the
endpoint, example response and subdomain setup instructions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Malek TellissiandClaude Sonnet 4.6 committed 2026-09-12 16:43:48 +02:00
1 parent 269570c7d6
commit e14baca7e0
5 files changed
+110 -6

No files matched your search

+75
View File
@@ -95,6 +95,80 @@ Rezepte sind versioniert und einem Produkt zugeordnet:
--- ---
## LOT-API
Öffentliche JSON-API zur Rückverfolgung von Chargen. Kein Auth erforderlich.
### Endpunkt
```
GET https://api.sms.spices.blue/api/v1/{lot_number}
```
### Beispiel-Antwort
```json
{
"data": {
"lot_number": "LOT-2024-001",
"produced_at": "2024-10-01",
"expires_at": "2026-10-01",
"units_produced": 50,
"quantity_g": 5000,
"product": {
"name": "Curry Klassik",
"description": "..."
},
"packing": {
"name": "Standbodenbeutel",
"size_g": 100,
"sku": "CUR-100",
"gtin": "4012345678901"
},
"recipe": {
"version": 3,
"created_at": "2024-09-15"
},
"spices": [
{
"name": "Kurkuma",
"percentage": 60.0,
"origin": {
"country": "Indien",
"farmer": "Singh Farm",
"supplier": "Gewürzhandel GmbH",
"notes": null
}
}
]
}
}
```
Fehler (nicht gefunden):
```json
{ "error": "LOT-Nummer nicht gefunden." }
```
### Subdomain-Setup auf dem Server
Der Container läuft intern auf `127.0.0.1:5000`. Der Host-Proxy (Nginx/Caddy) muss `api.sms.spices.blue` auf denselben Port zeigen — Beispiel für Nginx:
```nginx
server {
server_name api.sms.spices.blue;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
Danach SSL via `certbot --nginx -d api.sms.spices.blue` erneuern.
---
## Cronjob ## Cronjob
Auf dem Server einrichten, damit der Laravel-Scheduler läuft: Auf dem Server einrichten, damit der Laravel-Scheduler läuft:
@@ -198,4 +272,5 @@ Table batch_origins {
- [x] Shopify-Import (Produkte, Varianten, Lagerbestand, Bilder, Beschreibung, Preise) - [x] Shopify-Import (Produkte, Varianten, Lagerbestand, Bilder, Beschreibung, Preise)
- [x] Continuous Deployment via Gitea Actions + Docker - [x] Continuous Deployment via Gitea Actions + Docker
- [x] Frontend (Sidebar, Navigation, Barcode-Preview, Bild-Grid) - [x] Frontend (Sidebar, Navigation, Barcode-Preview, Bild-Grid)
- [x] LOT-API (JSON, öffentlich, Subdomain api.sms.spices.blue)
- [ ] Dashboard (Füllstände, Lager, Sales-Zeitspanne) - [ ] Dashboard (Füllstände, Lager, Sales-Zeitspanne)
@@ -13,6 +13,7 @@ class LotController extends Controller
{ {
$batch = Batch::with([ $batch = Batch::with([
'product', 'product',
'size.packing',
'recipe.spices', 'recipe.spices',
'origins.spice', 'origins.spice',
'origins.supplier', 'origins.supplier',
+13 -4
View File
@@ -10,15 +10,24 @@ class LotResource extends JsonResource
public function toArray(Request $request): array public function toArray(Request $request): array
{ {
return [ return [
'lot_number' => $this->lot_number, 'lot_number' => $this->lot_number,
'produced_at' => $this->produced_at?->toDateString(), 'produced_at' => $this->produced_at?->toDateString(),
'expires_at' => $this->expires_at?->toDateString(), 'expires_at' => $this->expires_at?->toDateString(),
'units_produced' => $this->units_produced,
'quantity_g' => (float) $this->quantity_g,
'product' => [ 'product' => [
'name' => $this->product->title, 'name' => $this->product->title,
'description' => $this->product->description, 'description' => $this->product->description,
], ],
'packing' => $this->size ? [
'name' => $this->size->packing->name,
'size_g' => (float) $this->size->packing->size,
'sku' => $this->size->sku,
'gtin' => $this->size->gtin,
] : null,
'recipe' => [ 'recipe' => [
'name' => $this->recipe->name, 'version' => $this->recipe->version,
'created_at' => $this->recipe->created_at->toDateString(),
], ],
'spices' => $this->recipe->spices->map(function ($spice) { 'spices' => $this->recipe->spices->map(function ($spice) {
$origin = $this->origins->firstWhere('spice_id', $spice->id); $origin = $this->origins->firstWhere('spice_id', $spice->id);
+19
View File
@@ -0,0 +1,19 @@
<?php
return [
'paths' => ['api/*'],
'allowed_methods' => ['GET'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['Content-Type', 'Accept'],
'exposed_headers' => [],
'max_age' => 86400,
'supports_credentials' => false,
];
+2 -2
View File
@@ -5,8 +5,8 @@ use App\Http\Controllers\ShopifyWebhookController;
use App\Http\Middleware\VerifyShopifyWebhook; use App\Http\Middleware\VerifyShopifyWebhook;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('/lot/{lot_number}', [LotController::class, 'show']) Route::get('/v1/{lot_number}', [LotController::class, 'show'])
->middleware('throttle:30,1') ->middleware('throttle:60,1')
->name('api.lot'); ->name('api.lot');
Route::post('/webhooks/shopify/orders-paid', [ShopifyWebhookController::class, 'ordersPaid']) Route::post('/webhooks/shopify/orders-paid', [ShopifyWebhookController::class, 'ordersPaid'])