Shop - BevvyTech/BrewskiDocs GitHub Wiki

Shop Administration API

Endpoints under the /shop prefix expose management tools for the storefront/homepage experience. All routes require an authenticated bearer token. Unless otherwise noted, only super-user/support teams (platform admins) may call them. Error responses follow the global { "message": "..." } convention.

ℹ️ In addition to the private routes below, /public/team-promotion now surfaces the currently promoted team (see Public Catalogue).

Shop Navigation

The shop navigation registry controls which UI elements appear in the public storefront header (and future sections such as footer or sidebars). Records are keyed by section (e.g. menu.top) and item (e.g. home, basket). Entries are auto-seeded when the list endpoint is called so operators always see the full set of known toggles.

GET /shop/navigation

  • Auth: Super-user or support team membership.
  • Response 200:
    {
      "items": [
        {
          "id": "2f35a0f4-26e3-4e3e-8d30-981862c4d9d3",
          "section": "menu.top",
          "item": "home",
          "enabled": true,
          "createdAt": "2025-07-25T10:30:00.000Z",
          "updatedAt": "2025-07-25T10:30:00.000Z"
        }
      ]
    }
  • Errors
    • 401 unauthenticated.
    • 403 caller lacks platform (super/support) access.

PATCH /shop/navigation/:id

Toggle a navigation entry on or off.

  • Auth: Super-user or support team membership.
  • Body:
    { "enabled": false }
  • Response 200: { "item": <object> } in the same shape as the list entry.
  • Errors
    • 400 invalid payload.
    • 401 unauthenticated.
    • 403 caller lacks platform (super/support) access.
    • 404 entry missing.

POST /shop/:teamId/publish-beers

Force every beer owned by the team to be visible on the public catalogue.

  • Auth: Team owner or admin of :teamId.
  • Body: none.
  • Response 200:
    { "updated": 17 }
    updated is the number of beers toggled to showPublic=true.
  • Errors
    • 401 unauthenticated.
    • 403 caller is not an owner/admin for the target team.
    • 404 team missing.

GET /shop/teams/search

Search teams when scheduling homepage promotions.

  • Auth: Super-user or support team membership.
  • Query params
    • q (string, required, ≥2 chars) – search term.
    • limit (1–25, default 10) – maximum matches.
  • Response 200:
    {
      "results": [
        {
          "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
          "name": "Lantern Brewery",
          "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png",
          "publicSlug": "lantern-brewery"
        }
      ]
    }

Upsell Catalogue

The upsell catalogue stores accessory products that can be promoted across the public shop. All routes below require super-user or support team membership. Coupler metadata uses the keg_coupler_type enum with values: d, s, g, a, u, m, keykeg, grundy, other.

GET /shop/upsells

List upsell records.

  • Auth: Super-user or support team membership.
  • Query params
    • includeInactive (optional boolean) – include records where isActive=false.
  • Response 200:
    {
      "upsells": [
        {
          "id": "1a2b3c4d-1111-2222-3333-444455556666",
          "slug": "s-type-coupler",
          "name": "S-Type Keg Coupler",
          "description": "Stainless S-type coupler supplied with check valve.",
          "priceMinor": 12900,
          "currencySymbol": "£",
          "price": "£129.00",
          "couplerType": "s",
          "displayOrder": 10,
          "images": [
            {
              "id": "img-01",
              "url": "https://assets.brewskiapp.com/upsells/1a2b3c4d/img-01-main.webp",
              "iconUrl": "https://assets.brewskiapp.com/upsells/1a2b3c4d/img-01-icon.webp",
              "isDefault": true
            }
          ]
        }
      ]
    }
    • price is provided when both priceMinor and currencySymbol are present; otherwise it is null.
    • images are ordered as stored; only one image carries isDefault: true.

GET /shop/upsells/:id

Fetch a single upsell. Returns the same payload shape as the list entry. Responds with 404 when the record is missing.

POST /shop/upsells

Create a new upsell.

  • Auth: Super-user or support team membership.
  • Body (JSON):
    • name (string, required, ≤160 chars)
    • slug (optional string; alphanumeric + hyphen)
    • description (optional string, ≤5000 chars)
    • priceMinor (optional integer, stored in minor currency units)
    • currencySymbol (optional string, ≤8 chars)
    • couplerType (optional enum from keg_coupler_type)
    • isActive (optional boolean, defaults to true)
    • displayOrder (optional integer, defaults to 0)
  • Response 201: { "upsell": <object> }
  • Errors: 400 invalid payload.

PATCH /shop/upsells/:id

Update an upsell. Accepts any subset of the fields listed above (all optional).

  • Auth: Super-user or support team membership.
  • Response 200: { "upsell": <object> }
  • Errors: 400 invalid payload, 404 upsell missing.

DELETE /shop/upsells/:id

Delete an upsell and remove any stored imagery.

  • Auth: Super-user or support team membership.
  • Response 204: No content.
  • Errors: 404 upsell missing.

POST /shop/upsells/:id/images

Upload an image for an upsell. The service stores a 500 × 500 primary asset and a 100 × 100 icon.

  • Auth: Super-user or support team membership.
  • Body: multipart/form-data
    • image (required JPEG/PNG/WEBP ≤5 MB)
    • setDefault (optional flag; accepts "true", "1", or "on")
  • Response 201: { "upsell": <object> } (with the refreshed images array).
  • Errors: 400 missing file, 413 image too large, 415 unsupported content type, 404 upsell missing.

PATCH /shop/upsells/:id/images/:imageId/default

Mark an uploaded image as the default.

  • Auth: Super-user or support team membership.
  • Response 200: { "upsell": <object> }
  • Errors: 404 upsell or image missing.

DELETE /shop/upsells/:id/images/:imageId

Remove an upsell image and delete the stored assets.

  • Auth: Super-user or support team membership.
  • Response 200: { "upsell": <object> } (remaining images; if the default is deleted the first remaining image becomes default).
  • Errors: 404 upsell or image missing.

GET /shop/homepage-promotions

List all scheduled homepage promotions.

  • Auth: Super-user or support team membership.
  • Response 200:
    {
      "promotions": [
        {
          "id": "c97c...",
          "teamId": "d43c...",
          "startsAt": "2025-04-01T08:00:00.000Z",
          "imageUrl": "https://assets.brewskiapp.com/promotions/lantern.png",
          "imageKey": "promotion-1738079482.jpg",
          "imageTitle": "Lantern rooftop pour",
          "imageDescription": "Hazy saison poured at sunset on the taproom roof.",
          "mainTitle": "Featured brewery",
          "mainDescription": "Lantern Brewery leads our spring showcase...",
          "textNeedsBackground": true,
          "textBackground": "dark",
          "textColor": "white",
          "isDefault": false,
          "createdAt": "2025-02-28T15:22:17.123Z",
          "updatedAt": "2025-02-28T15:22:17.123Z",
          "team": {
            "id": "d43c...",
            "name": "Lantern Brewery",
            "logoUrl": "https://assets.brewskiapp.com/logos/d43c.png",
            "publicSlug": "lantern-brewery"
          },
          "createdBy": {
            "id": "9c3b...",
            "name": "Sophie Brewer"
          }
        }
      ],
      "fallbackTeam": null
    }
    • fallbackTeam echoes the oldest team (id, name, slug, logo) when no promotions exist so the UI can highlight the default selection.
    • isDefault marks the platform fallback promotion; only one record may be default at any time.
    • textBackground returns one of dark, darkdeep, light, or lightdeep when a background overlay is required (dark/light = 15% alpha, darkdeep/lightdeep = 85% alpha).

POST /shop/homepage-promotions

Create a new promotion.

  • Auth: Super-user or support team membership.
  • Body: multipart/form-data with fields:
    • teamId (UUID, required)
    • startsAt (ISO string or YYYY-MM-DDTHH:mm, required)
    • image (JPEG/PNG/WEBP file ≤5 MB, required)
    • mainTitle (optional string ≤160 chars – send an empty string to clear an existing value)
    • mainDescription (optional string – empty string clears the value)
    • imageTitle (optional string ≤160 chars – empty string clears)
    • imageDescription (optional string – empty string clears)
    • textNeedsBackground (optional boolean; defaults to false)
    • textBackground (optional string; accepts dark, darkdeep, light, or lightdeep when textNeedsBackground is enabled. dark/light map to 15% overlays, darkdeep/lightdeep to 85%.)
    • textColor (optional string; accepts black, white, or rainbow)
  • Response 201: { "promotion": <object> } (same shape as list endpoint).
  • When the table has no default promotion, the first created record is automatically marked as default.
  • Errors: 400 missing fields, 413 image too large, 415 unsupported media type, 404 team not found.

PATCH /shop/homepage-promotions/:id

Update an existing promotion.

  • Auth: Super-user or support team membership.
  • Body options:
    • multipart/form-data (allows replacing the image and metadata).
    • JSON object containing any subset of teamId, startsAt, mainTitle, mainDescription, imageTitle, imageDescription, textNeedsBackground, textBackground, textColor (provide "" to null a text field).
  • Response 200: { "promotion": <object> } (updated record).
  • Errors: 400 no fields supplied, 404 promotion or team missing.

POST /shop/homepage-promotions/:id/default

Promote a scheduled item to be the platform default (demoting any existing default).

  • Auth: Super-user or support team membership.
  • Response 200:
    {
      "promotion": { "...": "..." },
      "demotedPromotionIds": ["a12f..."]
    }
    • promotion matches the list payload with isDefault: true.
    • demotedPromotionIds contains any promotions that were previously marked as default and are now cleared.
  • Errors: 404 when the promotion is missing.

DELETE /shop/homepage-promotions/:id

Remove a scheduled promotion (also deletes the stored image asset).

  • Auth: Super-user or support team membership.
  • Response 204: No content.
  • Errors: 400 when attempting to delete the default promotion, 404 promotion missing.

GET /shop/homepage-promotions/current

Fetch the promotion that should currently appear on the homepage. Primarily used by admin tooling.

  • Auth: Super-user or support team membership.
  • Response 200:
    {
      "promotion": {
        "id": "c97c...",
        "teamId": "d43c...",
        "startsAt": "2025-04-01T08:00:00.000Z",
        "imageUrl": "https://assets.brewskiapp.com/promotions/lantern.png",
        "imageTitle": "Lantern rooftop pour",
        "imageDescription": "Hazy saison poured at sunset on the taproom roof.",
        "mainTitle": "Featured brewery",
        "mainDescription": "Lantern Brewery leads our spring showcase...",
        "textNeedsBackground": true,
        "textBackground": "dark",
        "textColor": "white",
        "isDefault": false,
        "team": { ... }
      }
    }
    • If no promotion is active or scheduled, the default promotion is returned (if one exists); otherwise promotion is null.

GET /shop/homepage-hero

List all hero records that power the top banner carousel on the public shop homepage.

  • Auth: Super-user or support team membership.
  • Response 200:
    {
      "heroes": [
        {
          "id": "0f6e1fb2-0fa7-4e07-b994-0a28cf6c26d0",
          "category": "Marketplace highlight",
          "title": "Discover independent breweries",
          "message": "Fresh releases, direct from the source. Explore what’s pouring today.",
          "showButton": true,
          "buttonText": "Browse beers",
          "buttonLink": "/shop",
          "theme": "dark",
          "imageUrl": "https://assets.brewskiapp.com/platform/hero-1738345600.jpg",
          "imageKey": "hero-1738345600.jpg",
          "imageWidth": 1280,
          "imageHeight": 720,
          "displaySeconds": 8,
          "position": 0,
          "createdAt": "2025-06-02T10:05:44.123Z",
          "updatedAt": "2025-06-02T10:05:44.123Z"
        }
      ]
    }
    • Results are ordered by position (ascending) and then createdAt. Use these fields when rendering a carousel.
    • theme is either dark or light and informs the storefront which contrasting text colour to pick.
    • displaySeconds controls how long the hero should remain on-screen before the next slide fades in (defaults to 8 seconds if not supplied).
    • imageUrl points to a JPEG resized to a maximum height of 770 px; the original upload is discarded once transformed.

POST /shop/homepage-hero

Create a new hero slide.

  • Auth: Super-user or support team membership.
  • Body: multipart/form-data
    • category (string ≤160 chars, required)
    • title (string ≤200 chars, required)
    • message (string ≤5000 chars, required)
    • showButton (true/false, optional; defaults to false)
    • displaySeconds (integer 3–120, optional; defaults to 8)
    • buttonText (string ≤160 chars, required when showButton=true)
    • buttonLink (string ≤2048 chars, required when showButton=true, must be http(s)://… or start with /)
    • theme (dark or light, optional; defaults to dark)
    • image (JPEG/PNG/WEBP file ≤8 MB, required)
  • Response 201: { "hero": <object> } (fields as described above).
  • Behaviour
    • New slides are appended to the end of the carousel (position is set to the next highest value).
    • When displaySeconds is omitted the slide inherits the platform default (currently 8 s).
    • Uploaded images are rotated when necessary, resized to a 770 px height ceiling, and converted to progressive JPEG before storage.
  • Errors:
    • 400 missing required fields or invalid button link format.
    • 413 image exceeds 8 MB.
    • 415 unsupported media type.

PATCH /shop/homepage-hero/:id

Update an existing hero slide.

  • Auth: Super-user or support team membership.
  • Path params: id (hero UUID).
  • Body: Same multipart/form-data contract as the POST endpoint; omit image to keep the current artwork.
  • Response 200: { "hero": <object> } (updated record).
  • Errors:
    • 400 missing required fields or invalid button link format.
    • 404 hero not found.
    • 413 image exceeds 8 MB.
    • 415 unsupported media type.

POST /shop/homepage-hero/:id/reorder

Move a hero slide up or down within the carousel.

  • Auth: Super-user or support team membership.
  • Path params: id (hero UUID).
  • Body:
    { "direction": "up" } // or "down"
  • Response 200:
    {
      "heroes": [
        { "...": "..." }
      ]
    }
    • The returned array is the updated carousel order (same shape as GET /shop/homepage-hero).

DELETE /shop/homepage-hero/:id

Remove a hero slide and resequence the remaining carousel positions.

  • Auth: Super-user or support team membership.
  • Path params: id (hero UUID).
  • Response 200:
    { "heroes": [ /* refreshed list matching GET /shop/homepage-hero */ ] }
    • The response reflects the new ordering after the slide is removed and positions are compacted (0…n-1).
  • Behaviour:
    • Deletes the stored hero image asset after the record is removed.
    • Remaining slides keep their existing displaySeconds; only position is resequenced.
  • Errors:
    • 404 hero not found.

GET /shop/homepage-featured-groups

List the two homepage featured group slots. Each entry references a brewery team and one of its shop-visible brewery groups; the storefront consumes these records to promote collaborative buying programmes on the public homepage.

  • Auth: Super-user or support team membership.
  • Response 200:
    {
      "placements": [
        {
          "id": "f011b2e4-1234-4a6f-9b7e-0c1d2e3f4a5b",
          "position": 1,
          "team": {
            "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
            "name": "Lantern Brewery",
            "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png",
            "publicSlug": "lantern-brewery"
          },
          "group": {
            "id": "8cb5b1e0-4321-4af0-9d3a-0e9f7b24c6aa",
            "name": "Northern Alliance",
            "slug": "northern-alliance",
            "shopVisible": true,
            "status": "active",
            "imageUrl": null
          },
          "createdAt": "2025-07-28T09:00:00.000Z",
          "updatedAt": "2025-07-28T09:00:00.000Z"
        }
      ]
    }
  • Errors
    • 401 unauthenticated.
    • 403 caller lacks platform (super/support) access.

PUT /shop/homepage-featured-groups/:position

Create or replace a featured group slot.

  • Auth: Super-user or support team membership.
  • Path params
    • position (1 or 2) — slot index, counting from the top of the services column.
  • Body:
    { "teamId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "groupId": "8cb5b1e0-4321-4af0-9d3a-0e9f7b24c6aa" }
    • teamId must reference an existing brewery.
    • groupId must belong to that brewery via brewery_group_members with status active or invited, and the group must have shop_visible=true.
  • Response 200: { "placement": <object> } with the same fields as GET /shop/homepage-featured-groups.
  • Errors
    • 400 invalid payload (missing IDs, or the selected group is not linked to the team / not shop-visible).
    • 401 unauthenticated.
    • 403 caller lacks platform (super/support) access.
    • 409 the supplied group is already assigned to the other slot.

DELETE /shop/homepage-featured-groups/:position

Clear a featured group slot.

  • Auth: Super-user or support team membership.
  • Path params
    • position (1 or 2).
  • Response 204: No content (slot cleared).
  • Errors
    • 401 unauthenticated.
    • 403 caller lacks platform (super/support) access.

GET /shop/teams/:teamId/groups

List the brewery groups that a given team currently belongs to (filtered to shopVisible groups). Used by the homepage editor to populate the second dropdown after selecting a brewery.

  • Auth: Super-user or support team membership.
  • Path params
    • teamId — target brewery UUID.
  • Response 200:
    {
      "groups": [
        {
          "id": "8cb5b1e0-4321-4af0-9d3a-0e9f7b24c6aa",
          "name": "Northern Alliance",
          "slug": "northern-alliance",
          "shopVisible": true,
          "status": "active",
          "imageUrl": null,
          "membershipStatus": "active"
        }
      ]
    }
  • Errors
    • 401 unauthenticated.
    • 403 caller lacks platform (super/support) access.
⚠️ **GitHub.com Fallback** ⚠️