Skip to content

Backend and request flow ​

This page describes how the Meduse Laravel source registers HTTP routes, authenticates API calls, selects a tenant, and handles several catalog writes. The evidence is the pinned source revision linked below; it does not establish a deployed host, active database, queue health, or production behavior. See the API endpoint inventory, the separately mapped frontend interfaces, and the workflows, connectors, queues, and scheduled jobs pages.

Route registration and middleware ​

The application RouteServiceProvider mounts routes/api.php under the /api prefix and the api middleware group. It mounts routes/web.php with the web group. The API group in Kernel.php applies throttle:api and route-model binding. The provider's limiter is configured for 1,200 requests per minute. That is source configuration, not a measured service limit.

Most routes in routes/api.php are inside one auth:sanctum and TenantMiddleware group. POST /api/login sits outside that group and requires only the API middleware. The route-level declarations and effective prefix are itemized in the endpoint inventory.

Each enabled module with a route provider adds its own API and web route files. For example, the Menzzo module provider mounts those files with /api plus api middleware for API routes and web for web routes. Module API route files then add auth:api. The checked module API files contain one authenticated identity route apiece; the commented Vidaxl catalog routes are not active declarations. modules_statuses.json marks the route-bearing modules enabled in this source snapshot. Module landing pages use their own URI prefixes. These are source registrations; no Laravel route table was generated.

The TenancyServiceProvider also loads routes/tenant.php. That file declares a domain-initialized tenant GET / page using web, InitializeTenancyByDomain, and PreventAccessFromCentralDomains. The ordinary web route file also declares GET /; route selection between those two registrations is not established here.

Queue connection and request timing ​

Controllers pass queue names such as processing, export, and export-product-files to Queue::push; a queue name alone does not establish asynchronous execution. config/queue.php defaults the queue connection to sync, while QUEUE_CONNECTION can override it. The effective runtime value was not inspected. With the sync connection, a pushed job runs inline and the controller waits for it; with a deferred connection, the controller returns after enqueueing and a worker must process it. Treat job IDs and trigger messages as controller responses, not proof of background execution or external completion.

Authentication and tenant selection ​

The login request validates required string email and password fields. On successful Auth::attempt, LoginController creates a Sanctum token named meduse and returns HTTP 200 with {success, data: {token, user}, message}. Failed credentials return HTTP 401 with {success: false, message: "Unauthorised"}. The route is not inside the tenant middleware group, so the source does not require AX-Tenant for login.

Protected API routes declare auth:sanctum before TenantMiddleware. The custom middleware checks the AX-Tenant header: absent, literal undefined, and literal null values pass through without initializing a tenant; another supplied value is looked up as a Tenant ID. A match initializes tenancy before the controller runs; a miss returns HTTP 404 {error: "Tenant not found"}. After the controller returns, it calls Tenancy::end(). This header chooses the request tenant in the source flow; the middleware does not bind that choice to a tenant claim on the authenticated user.

The tenancy configuration selects database, cache, filesystem, and queue bootstrappers. Database names are configured as tenant plus the tenant ID. The effective database connection and tenant data remain environment-dependent and were not inspected. The route group does not declare the package's stateful Sanctum frontend middleware; cookie-based SPA behavior should not be inferred from these declarations.

Product, attribute, and category behavior ​

POST /api/list-all-products delegates from CatalogProductController to ProductQueryService. The service builds the catalog listing from database queries. The listing path is read-only in the inspected implementation. Product data is represented by CatalogProduct, including JSON raw_values; model create/update hooks also synchronize flat product data and record raw-value changes.

The three product bulk-update routes push UpdateProducts, UpdateProductsExcel, or UpdateProductsNames jobs to the named processing queue, notify the authenticated user, and return a tracked job ID. Their execution timing depends on the effective queue connection: the source default is sync, which runs the job inline, but QUEUE_CONNECTION may select a deferred connection. The response and tracked job record do not by themselves establish which connection ran or whether catalog changes completed.

CatalogAttributeController::saveAttributes, exposed as POST /api/save-attribute, creates or updates the catalog attribute, family associations, translations, and option/value records. It then pushes a catalog-attribute synchronization job to the named export queue for the central context or the meuble_promo tenant. As with other queue pushes, the effective connection determines whether the job runs inline or is deferred; the HTTP response does not prove downstream catalog state.

Category listing and menu-building actions in CatalogCategoryController read catalog/category and menu records. The parent-category toggles update menu.use_parent_category. Shopify category create/parent/name handlers select the fixed mosaikasa tenant when present and delegate to ShopifyService; the provider-side result is not established by the controller trace. GET /api/update-shopify-menu pushes an UpdateShopifyMenu job to the named export queue, then returns a trigger message after Queue::push returns. Depending on the effective connection, that push may run the job inline or defer it. GET /api/update-make-up-menu calls Artisan::call('makeup:update-menu') inline and returns captured command output; the controller does not inspect the command exit code. Both are state-changing triggers despite using GET, and the source response does not establish external provider state.

MenuController::saveMenu, exposed as POST /api/menu/save-menu, normalizes the submitted category lists, persists child positions and parent ordering, and updates menu rows and category-attribute positions. When a category image path points into a temporary location, the controller attempts to move the file under category media and stores the resulting image reference. The move is wrapped in a catch that suppresses the exception, so a success response alone does not prove that every referenced image moved.

POST /api/menu/upload-category-image stages files on the public disk under the temporary menu-category image directory and returns their paths. Product-image upload follows a two-step flow in UploadProductImagesController: POST /api/upload-images stages UUID-named files under media/catalog/product/tmp/{product_id}; POST /api/save-images validates add/update/remove operations, moves only new staged files into product-media storage, updates the product's raw_values, saves the catalog row, and dispatches ProductImagesUpdated for changed product IDs. Remove operations clear image references from raw_values; they do not delete the stored file. Caught outer failures and partial per-image failures return HTTP 200 JSON with success: false and a message, because the controller does not set an error status.

The product image ZIP handler checks the download-product-images-zip Gate, reads image roles in product raw_values through its ZIP service, skips invalid or missing files, creates a temporary ZIP, and streams it for download. The controller returns 403 when access is denied and 404 when no usable images are available. Product file upload has separate /api/upload-file and /api/save-files handlers in UploadProductFileController: the first stages files on the public disk, while the second moves newly staged GIF/PDF files and updates the corresponding raw_values references. Removal clears the reference but does not delete the stored file; removing a video clears its URL. The controller pushes ProductFilesProcessor to the named export-product-files queue for affected product IDs. That processor reads current product references, writes a CSV, and uploads it to the configured FTP pending directory. Whether it runs inline or through a worker depends on the effective queue connection; the FTP upload was not exercised.

Response and error behavior ​

There is no single response envelope for the whole API. Login and invalid-tenant responses use explicit status codes, while several controllers return arrays that Laravel serializes with its default status. The translation closure catches an exception and returns {success: false, message} without choosing an error status; Laravel therefore uses its default HTTP 200. The image upload controller's caught outer and partial-operation failures likewise return HTTP 200 JSON with success: false. Other endpoints have their own 400, 403, and 404 branches. The application exception handler adds no custom rendering behavior in the inspected range. FormRequest validation and unauthenticated responses otherwise rely on Laravel/Sanctum defaults; those framework responses were not exercised in a running application.

Where to change behavior ​

Start at the active route declaration in routes/api.php or the owning module's Routes/api.php. For product listings, follow CatalogProductController to ProductQueryService; for attribute writes, start at CatalogAttributeController; for category changes, start at CatalogCategoryController; for menu persistence, start at MenuController; and for product image writes, start at UploadProductImagesController. Follow each queue push to its job and event dispatch to its listener; whether queued work is deferred depends on the effective queue connection described above. See the queue page. Tenant selection starts in TenantMiddleware and tenancy configuration. The API inventory links route groups, and the frontend interface page maps client calls separately.

Evidence boundary ​

These pages were reviewed against axelites/menzzo-meduse commit 5d3de6cb63b976aaeef739a8a7f288c4b46509ad. No Laravel boot, Artisan command, route listing, build, database, host, credential, or production access was performed. The page proves source declarations and code paths only; deployed configuration, runtime route order, tenant existence, queue execution, provider effects, and rendered UI behavior remain outside this evidence.

Internal developer documentation for Menzzo systems.