Teams - BevvyTech/BrewskiDocs GitHub Wiki

Teams

Method Path Description
POST /teams Create a new team and assign the caller as owner.
POST /teams/:teamId/logo Upload (replace) a team logo (multipart/form-data).
GET /teams/:teamId/logo Fetch the stored team logo file.
DELETE /teams/:teamId/logo Remove the current team logo.
DELETE /teams/:teamId/members/:userId Soft-deactivate a team member (non-owner).
POST /teams/:teamId/members/:userId/owner Promote a member to owner (confirmation required).

POST /teams

  • Auth: Bearer token (any authenticated user).
  • Body (application/json):
    {
      "name": "Lantern Brewery",
      "currencySymbol": "£",
      "locale": "en-GB",
      "temperatureUnit": "celsius",
      "brewhouseCapacity": 1200,
      "obscureBatchCode": true,
      "obscureOrderNumber": true,
      "billingName": "Lantern Brewery Ltd",
      "defaultPaymentTermsAmount": 30,
      "defaultPaymentTermsUnit": "days"
    }
  • Response 201:
    {
      "team": {
        "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
        "name": "Lantern Brewery",
        "logoUrl": null,
        "currencySymbol": "£",
        "temperatureUnit": "celsius",
        "locale": "en-GB",
        "obscureBatchCode": true,
        "obscureOrderNumber": true,
        "brewhouseCapacity": 1200,
        "billingName": "Lantern Brewery Ltd",
        "billingAddress": null,
        "vatNumber": null,
        "paymentDetails": null,
        "invoiceTerms": null,
        "defaultPaymentTermsAmount": 30,
        "defaultPaymentTermsUnit": "days",
        "defaultContainers": [
          { "id": "a90c...", "selected": true },
          { "id": "f31a...", "selected": true }
        ],
        "createdAt": "2025-02-12T11:45:00.000Z",
        "updatedAt": "2025-02-12T11:45:00.000Z"
      },
      "membership": {
        "id": "0fdb...",
        "teamId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
        "userId": "3f3fdd2e-1bab-49e3-9d24-8a2d0b307d1d",
        "role": "owner",
        "deactivated": false,
        "createdAt": "2025-02-12T11:45:00.000Z",
        "updatedAt": "2025-02-12T11:45:00.000Z"
      }
    }
  • Behavior:
  • Seeds default containers (30 L keg, 9 g cask, 440 ml can, 500 ml bottle) and marks them as selected.
  • Creates a default pricebook for the team when none exists.
  • Enables obscureBatchCode and obscureOrderNumber so new teams start with anonymised identifiers.
  • Returns 409 if a team with the same name already exists.

POST /teams/:teamId/logo

  • Auth: Bearer token. Caller must be member of the team.
  • Body: multipart/form-data with a single file field (logo). PNG, JPEG, and SVG accepted. Max 3 MB; larger uploads return 413. The original upload is not stored—Brewski generates a 500 px-tall logo and a 100 px icon automatically (aspect ratio preserved).
  • Response 200:
    {
      "logoUrl": "/assets/logos/d43c046a.png",
      "logoIconUrl": "/assets/logos/d43c046a-icon.png"
    }
  • Errors: 400 (missing file), 413 (file too large), 415 (unsupported type), 404 (team missing).

GET /teams/:teamId/logo

  • Auth: Bearer token required.
  • Response 200: Binary image stream with proper Content-Type.
  • Errors: 404 when logo absent.

DELETE /teams/:teamId/logo

  • Auth: Bearer token required.
  • Response 204: Logo deleted if present.

DELETE /teams/:teamId/members/:userId

  • Auth: Bearer token.
  • Rules: Owners can remove any non-owner; members can remove themselves.
  • Response 204

POST /teams/:teamId/members/:userId/owner

  • Auth: Bearer token, acting user must be an existing owner.
  • Body:
    {
      "confirmation": "AGREE"
    }
  • Response 200:
    {
      "membership": {
        "id": "1b2c...",
        "userId": "8d7e...",
        "teamId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
        "role": "owner",
        "deactivated": false,
        "updatedAt": "2025-02-04T12:10:54.321Z"
      }
    }
  • Errors: 403 (caller not owner), 404 (member not found), 400 (missing confirmation).

POST /teams/:teamId/demo-seed

  • Auth: Bearer token. Caller must belong to the team and hold a super/support membership.
  • Query params
    • size (optional, small | medium | large — default medium): Controls how many records are generated.
    • intent (optional, seed-demo | fix-beer-categories — default seed-demo): Switch between the legacy demo seeding flow and the BJCP category maintenance task.
  • Body (application/json, optional):
    {
      "styleNames": [
        "1A American Light Lager",
        "21B Specialty IPA"
      ]
    }
    • Only honoured when intent=seed-demo. Omit the body to let the platform use its internal style list.
  • Response 200 (intent=seed-demo):
    {
      "teamId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
      "teamRole": "demo",
      "intent": "seed-demo",
      "migrations": [
        { "id": "20250201_seed_clients", "applied": true },
        { "id": "20250201_seed_orders", "applied": true }
      ],
      "appliedCount": 2,
      "counts": {
        "randomBeers": 24,
        "randomClients": 30,
        "randomOrders": 62,
        "randomBatches": 48
      },
      "randomSeed": {
        "size": "medium",
        "beersCreated": 24,
        "clientsCreated": 30,
        "ordersCreated": 62,
        "batchesCreated": 48
      }
    }
  • Response 200 (intent=fix-beer-categories):
    {
      "teamId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7",
      "teamRole": "demo",
      "intent": "fix-beer-categories",
      "updatedBeers": 42,
      "assignments": [
        {
          "code": "21B",
          "name": "Specialty IPA",
          "categoryId": "21",
          "categoryName": "IPA",
          "count": 8
        }
      ]
    }
  • Behaviour
    • Seeds demo migrations, then generates random data sized to the requested volume.
    • When styleNames are supplied the generator preferentially pulls beer styles from that list; duplicates are ignored.
    • Marks the team as demo once seeding succeeds.
    • With intent=fix-beer-categories, every beer in the team is reassigned to a random BJCP substyle and the update summary is logged to team activity; no demo data is created.
  • Errors: 401 unauthenticated, 403 missing membership or super/support role, 404 team missing, 400 malformed payload or no active members.

GET /support/tickets

List support tickets visible to the caller with the shared pagination envelope so Admin surfaces stay consistent.

Authentication & Permissions

Bearer token required.

  • scope=my (default) returns tickets the caller raised.
  • scope=team returns tickets for the supplied teamId (caller must be an owner/admin of that team or a support operator).
  • scope=all requires a support/super-user membership.

Query Parameters

  • scope (optional, my | team | all, default my)
  • teamId (UUID, required when scope=team, optional otherwise)
  • status (optional, repeatable pending | open | resolved | cancelled strings) — filters tickets by status.
  • priority (optional, low | normal | high | urgent)
  • assigned (optional, user UUID) — filter by assigned support user.
  • search (optional, string) — case-insensitive match against subject, description, requester name/email, and client name.
  • page (int ≥ 1, default 1) and pageSize (int ≥ 1, default 25, max 200).
  • limit (int ≤ 50, optional) — when supplied, returns the most recent limit tickets without pagination metadata (legacy behaviour used by the support menu).
  • includeMessages (optional, boolean) — when true, returns the most recent message for each ticket.

Response

200 OK

{
  "page": 1,
  "pageSize": 25,
  "total": 8,
  "pages": 1,
  "results": [
    {
      "id": "aaaa4444-bbbb-4ccc-8ddd-eeeeffff1111",
      "team": { "id": "team-1", "name": "Lantern Brewery", "role": "brewery" },
      "requester": { "id": "user-1", "name": "Alex", "email": "[email protected]" },
      "subject": "Need delivery labels",
      "status": "open",
      "priority": "high",
      "lastMessageAt": "2025-02-10T11:24:00.000Z",
      "messageCount": 3,
      "metadata": {},
      "messages": []
    }
  ],
  "tickets": [
    "...same array for backwards compatibility..."
  ]
}

When limit is provided the endpoint still returns page=1, pages=1, and pageSize=limit, with total equal to the number of returned records. New clients should omit limit and rely on page / pageSize.

Error Codes

401 Unauthorized — bearer token missing or invalid.
403 Forbidden — caller lacks the required scope/team permissions.
404 Not Found — supplied teamId is not visible to the caller.

⚠️ **GitHub.com Fallback** ⚠️