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:
1 parent
269570c7d6
commit
e14baca7e0
5 files changed
+107
-3
No files matched your search
@@ -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,12 +13,21 @@ class LotResource extends JsonResource
|
|||||||
'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);
|
||||||
|
|||||||
@@ -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
@@ -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'])
|
||||||
|
|||||||
Reference in new issue
Block a user