feat: api v1 garden bangs routes and openapi
This commit is contained in:
+36
-9
@@ -1,19 +1,27 @@
|
||||
# famapp HTTP API (v1)
|
||||
|
||||
REST JSON API under `/api/v1/`. Full OpenAPI spec is planned in task 87.3.
|
||||
REST JSON API under `/api/v1/`. Full specification: [`openapi.yaml`](openapi.yaml).
|
||||
|
||||
## Authentication
|
||||
|
||||
Every endpoint accepts **either**:
|
||||
|
||||
- Auth.js session cookie (browser login), or
|
||||
- `Authorization: Bearer <household-api-token>` header
|
||||
- `Authorization: Bearer <household-api-token>` header (recommended for external clients)
|
||||
|
||||
Unauthorized requests return `401` with `{ "error": "Unauthorized" }`.
|
||||
|
||||
Generate bearer tokens in **Settings → API tokens**.
|
||||
Generate bearer tokens in **Settings → API tokens**. Example:
|
||||
|
||||
## Endpoints (87.2)
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_TOKEN" https://fam.ginnoir.com/api/v1/calendars
|
||||
```
|
||||
|
||||
## OpenAPI
|
||||
|
||||
See [`openapi.yaml`](openapi.yaml) for paths, request/response schemas, and the `bearerAuth` security scheme. Import into Swagger UI, Postman, or your HTTP client of choice.
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Calendars
|
||||
|
||||
@@ -59,11 +67,30 @@ Generate bearer tokens in **Settings → API tokens**.
|
||||
| PATCH | `/api/v1/notes/:id` | Update note |
|
||||
| DELETE | `/api/v1/notes/:id` | Delete note |
|
||||
|
||||
### Garden
|
||||
|
||||
| Method | Path | Description |
|
||||
| ------ | ------------------------------- | -------------------------------------- |
|
||||
| GET | `/api/v1/garden/containers` | List containers |
|
||||
| POST | `/api/v1/garden/containers` | Create container |
|
||||
| GET | `/api/v1/garden/containers/:id` | Get container with plants |
|
||||
| PATCH | `/api/v1/garden/containers/:id` | Update container |
|
||||
| DELETE | `/api/v1/garden/containers/:id` | Delete container |
|
||||
| GET | `/api/v1/garden/plants` | List plants (optional `?containerId=`) |
|
||||
| POST | `/api/v1/garden/plants` | Create plant |
|
||||
| GET | `/api/v1/garden/plants/:id` | Get plant detail |
|
||||
| PATCH | `/api/v1/garden/plants/:id` | Update plant |
|
||||
| DELETE | `/api/v1/garden/plants/:id` | Delete plant |
|
||||
|
||||
### Bangs
|
||||
|
||||
| Method | Path | Description |
|
||||
| ------ | ------------------- | ------------------------------------------------ |
|
||||
| GET | `/api/v1/bangs` | Stats: `total` + `recent` (optional `?limit=10`) |
|
||||
| POST | `/api/v1/bangs` | Record bang (optional `occurredOn` YYYY-MM-DD) |
|
||||
| PATCH | `/api/v1/bangs/:id` | Update `occurredOn` |
|
||||
| DELETE | `/api/v1/bangs/:id` | Delete bang |
|
||||
|
||||
## Bearer token visibility
|
||||
|
||||
Bearer tokens see **household-visible calendars only** (private calendars are hidden). Mutations on calendars require ownership (session) or household visibility (bearer). Activity log records `actorId: null` for bearer mutations.
|
||||
|
||||
## Planned (87.3)
|
||||
|
||||
- Garden, bangs routes
|
||||
- OpenAPI specification
|
||||
|
||||
@@ -0,0 +1,829 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: famapp API
|
||||
version: "1.0.0"
|
||||
description: |
|
||||
Household-scoped REST JSON API for famapp (fam.ginnoir.com).
|
||||
|
||||
Authenticate with an Auth.js session cookie (browser) or a household API bearer token
|
||||
(external clients). Generate bearer tokens in Settings → API tokens.
|
||||
|
||||
servers:
|
||||
- url: https://fam.ginnoir.com
|
||||
description: Production
|
||||
- url: http://localhost:3000
|
||||
description: Local development
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
- sessionCookie: []
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
description: Household API token from Settings → API tokens
|
||||
sessionCookie:
|
||||
type: apiKey
|
||||
in: cookie
|
||||
name: authjs.session-token
|
||||
description: Auth.js session cookie (browser login)
|
||||
|
||||
schemas:
|
||||
Error:
|
||||
type: object
|
||||
required: [error]
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
|
||||
OkResponse:
|
||||
type: object
|
||||
required: [ok]
|
||||
properties:
|
||||
ok:
|
||||
type: boolean
|
||||
enum: [true]
|
||||
|
||||
Calendar:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
name: { type: string }
|
||||
color: { type: string, nullable: true }
|
||||
visibility: { type: string, enum: [private, household] }
|
||||
ownerId: { type: string, format: uuid }
|
||||
|
||||
CalendarInput:
|
||||
type: object
|
||||
required: [name]
|
||||
properties:
|
||||
name: { type: string }
|
||||
color: { type: string, nullable: true }
|
||||
visibility: { type: string, enum: [private, household], default: household }
|
||||
|
||||
CalendarEvent:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
calendarId: { type: string, format: uuid }
|
||||
title: { type: string }
|
||||
startAt: { type: string, format: date-time }
|
||||
endAt: { type: string, format: date-time }
|
||||
allDay: { type: boolean }
|
||||
location: { type: string, nullable: true }
|
||||
notes: { type: string, nullable: true }
|
||||
|
||||
EventInput:
|
||||
type: object
|
||||
required: [calendarId, title, startAt, endAt]
|
||||
properties:
|
||||
calendarId: { type: string, format: uuid }
|
||||
title: { type: string }
|
||||
startAt: { type: string, format: date-time }
|
||||
endAt: { type: string, format: date-time }
|
||||
allDay: { type: boolean, default: false }
|
||||
location: { type: string, nullable: true }
|
||||
notes: { type: string, nullable: true }
|
||||
remindMinutesBefore: { type: integer, nullable: true }
|
||||
|
||||
ListSummary:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
type: { type: string }
|
||||
name: { type: string }
|
||||
archived: { type: boolean }
|
||||
openCount: { type: integer }
|
||||
doneCount: { type: integer }
|
||||
createdAt: { type: string, format: date-time }
|
||||
|
||||
ListInput:
|
||||
type: object
|
||||
required: [type, name]
|
||||
properties:
|
||||
type: { type: string }
|
||||
name: { type: string }
|
||||
|
||||
ListItem:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
listId: { type: string, format: uuid }
|
||||
text: { type: string }
|
||||
qty: { type: string, nullable: true }
|
||||
notes: { type: string, nullable: true }
|
||||
done: { type: boolean }
|
||||
dueAt: { type: string, format: date-time, nullable: true }
|
||||
assigneeId: { type: string, format: uuid, nullable: true }
|
||||
position: { type: integer }
|
||||
|
||||
ItemInput:
|
||||
type: object
|
||||
required: [text]
|
||||
properties:
|
||||
text: { type: string }
|
||||
qty: { type: string, nullable: true }
|
||||
notes: { type: string, nullable: true }
|
||||
dueAt: { type: string, format: date-time, nullable: true }
|
||||
assigneeId: { type: string, format: uuid, nullable: true }
|
||||
metadata: { type: object, nullable: true }
|
||||
|
||||
Note:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
householdId: { type: string, format: uuid }
|
||||
authorId: { type: string, format: uuid }
|
||||
title: { type: string }
|
||||
body: { type: string }
|
||||
pinned: { type: boolean }
|
||||
remindAt: { type: string, format: date-time, nullable: true }
|
||||
createdAt: { type: string, format: date-time }
|
||||
updatedAt: { type: string, format: date-time }
|
||||
|
||||
NoteInput:
|
||||
type: object
|
||||
required: [title]
|
||||
properties:
|
||||
title: { type: string }
|
||||
body: { type: string, default: "" }
|
||||
pinned: { type: boolean, default: false }
|
||||
remindAt: { type: string, format: date-time, nullable: true }
|
||||
|
||||
GardenContainer:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
householdId: { type: string, format: uuid }
|
||||
name: { type: string }
|
||||
type: { type: string }
|
||||
locationNotes: { type: string, nullable: true }
|
||||
coverImageUrl: { type: string, nullable: true }
|
||||
images: { type: array, items: { type: string } }
|
||||
plantCount: { type: integer }
|
||||
createdAt: { type: string, format: date-time }
|
||||
updatedAt: { type: string, format: date-time }
|
||||
|
||||
GardenContainerDetail:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/GardenContainer"
|
||||
- type: object
|
||||
properties:
|
||||
plants:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/PlantSummary"
|
||||
|
||||
ContainerInput:
|
||||
type: object
|
||||
required: [name]
|
||||
properties:
|
||||
name: { type: string }
|
||||
type: { type: string, default: other }
|
||||
locationNotes: { type: string, nullable: true }
|
||||
coverImageUrl: { type: string, nullable: true }
|
||||
|
||||
PlantSummary:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
name: { type: string }
|
||||
scientificName: { type: string, nullable: true }
|
||||
healthStatus: { type: string }
|
||||
primaryImageUrl: { type: string, nullable: true }
|
||||
category: { type: string }
|
||||
|
||||
PlantListItem:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
containerId: { type: string, format: uuid, nullable: true }
|
||||
containerName: { type: string, nullable: true }
|
||||
name: { type: string }
|
||||
healthStatus: { type: string }
|
||||
primaryImageUrl: { type: string, nullable: true }
|
||||
category: { type: string }
|
||||
lastWateredAt: { type: string, format: date-time, nullable: true }
|
||||
hasOverdueCare: { type: boolean }
|
||||
|
||||
PlantDetail:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
householdId: { type: string, format: uuid }
|
||||
containerId: { type: string, format: uuid, nullable: true }
|
||||
containerName: { type: string, nullable: true }
|
||||
name: { type: string }
|
||||
scientificName: { type: string, nullable: true }
|
||||
speciesId: { type: string, nullable: true }
|
||||
category: { type: string }
|
||||
notes: { type: string, nullable: true }
|
||||
acquisitionDate: { type: string, nullable: true }
|
||||
growthStage: { type: string, nullable: true }
|
||||
healthStatus: { type: string }
|
||||
sunlight: { type: string, nullable: true }
|
||||
wateringNotes: { type: string, nullable: true }
|
||||
fertilizingNotes: { type: string, nullable: true }
|
||||
primaryImageUrl: { type: string, nullable: true }
|
||||
images: { type: array, items: { type: string } }
|
||||
createdAt: { type: string, format: date-time }
|
||||
updatedAt: { type: string, format: date-time }
|
||||
|
||||
PlantInput:
|
||||
type: object
|
||||
required: [name]
|
||||
properties:
|
||||
name: { type: string }
|
||||
category: { type: string, default: other }
|
||||
containerId: { type: string, format: uuid, nullable: true }
|
||||
healthStatus: { type: string, default: healthy }
|
||||
growthStage: { type: string, nullable: true }
|
||||
scientificName: { type: string, nullable: true }
|
||||
speciesId: { type: string, nullable: true }
|
||||
sunlight: { type: string, nullable: true }
|
||||
wateringNotes: { type: string, nullable: true }
|
||||
fertilizingNotes: { type: string, nullable: true }
|
||||
notes: { type: string, nullable: true }
|
||||
acquisitionDate: { type: string, nullable: true }
|
||||
images: { type: array, items: { type: string }, default: [] }
|
||||
primaryImageUrl: { type: string, nullable: true }
|
||||
|
||||
BangStats:
|
||||
type: object
|
||||
properties:
|
||||
total: { type: integer }
|
||||
recent:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/BangSummary"
|
||||
|
||||
BangSummary:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
occurredOn: { type: string, format: date, description: YYYY-MM-DD }
|
||||
recordedByName: { type: string, nullable: true }
|
||||
|
||||
Bang:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
occurredOn: { type: string, format: date }
|
||||
recordedBy: { type: string, format: uuid, nullable: true }
|
||||
createdAt: { type: string, format: date-time }
|
||||
|
||||
BangInput:
|
||||
type: object
|
||||
properties:
|
||||
occurredOn:
|
||||
type: string
|
||||
format: date
|
||||
description: YYYY-MM-DD; defaults to today
|
||||
|
||||
BangUpdateInput:
|
||||
type: object
|
||||
required: [occurredOn]
|
||||
properties:
|
||||
occurredOn: { type: string, format: date }
|
||||
|
||||
paths:
|
||||
/api/v1/calendars:
|
||||
get:
|
||||
summary: List visible calendars
|
||||
tags: [Calendars]
|
||||
responses:
|
||||
"200":
|
||||
description: Calendar list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Calendar" }
|
||||
"401":
|
||||
{
|
||||
description: Unauthorized,
|
||||
content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
|
||||
}
|
||||
post:
|
||||
summary: Create calendar
|
||||
tags: [Calendars]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/CalendarInput" }
|
||||
responses:
|
||||
"201":
|
||||
description: Created calendar
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Calendar" }
|
||||
"401": { description: Unauthorized }
|
||||
|
||||
/api/v1/calendars/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: Get calendar
|
||||
tags: [Calendars]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Calendar" }
|
||||
"404": { description: Not found }
|
||||
patch:
|
||||
summary: Update calendar
|
||||
tags: [Calendars]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
visibility: { type: string, enum: [private, household] }
|
||||
color: { type: string, nullable: true }
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Calendar" }
|
||||
delete:
|
||||
summary: Delete calendar
|
||||
tags: [Calendars]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
/api/v1/events:
|
||||
get:
|
||||
summary: List events in date range
|
||||
tags: [Events]
|
||||
parameters:
|
||||
- name: from
|
||||
in: query
|
||||
required: true
|
||||
schema: { type: string, format: date-time }
|
||||
- name: to
|
||||
in: query
|
||||
required: true
|
||||
schema: { type: string, format: date-time }
|
||||
- name: calendarIds
|
||||
in: query
|
||||
description: "all or comma-separated UUIDs"
|
||||
schema: { type: string, default: all }
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/CalendarEvent" }
|
||||
"400": { description: Missing from/to }
|
||||
post:
|
||||
summary: Create event
|
||||
tags: [Events]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/EventInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/CalendarEvent" }
|
||||
|
||||
/api/v1/events/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: Get event
|
||||
tags: [Events]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/CalendarEvent" }
|
||||
patch:
|
||||
summary: Update event
|
||||
tags: [Events]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/EventInput"
|
||||
description: All fields optional for PATCH
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/CalendarEvent" }
|
||||
delete:
|
||||
summary: Delete event
|
||||
tags: [Events]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
/api/v1/lists:
|
||||
get:
|
||||
summary: List lists
|
||||
tags: [Lists]
|
||||
parameters:
|
||||
- name: type
|
||||
in: query
|
||||
schema: { type: string }
|
||||
description: Filter by list type (e.g. shopping, tasks)
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/ListSummary" }
|
||||
post:
|
||||
summary: Create list
|
||||
tags: [Lists]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ListInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ListSummary" }
|
||||
|
||||
/api/v1/lists/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: Get list with items
|
||||
tags: [Lists]
|
||||
responses:
|
||||
"200":
|
||||
description: List detail including items array
|
||||
patch:
|
||||
summary: Update list name or archive
|
||||
tags: [Lists]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
archived: { type: boolean }
|
||||
responses:
|
||||
"200": { description: Updated list }
|
||||
delete:
|
||||
summary: Archive list
|
||||
tags: [Lists]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
/api/v1/lists/{id}/items:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: List items
|
||||
tags: [Lists]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/ListItem" }
|
||||
post:
|
||||
summary: Add item
|
||||
tags: [Lists]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ItemInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ListItem" }
|
||||
|
||||
/api/v1/lists/{id}/items/{itemId}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
- name: itemId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
patch:
|
||||
summary: Update or toggle item
|
||||
tags: [Lists]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
text: { type: string }
|
||||
qty: { type: string, nullable: true }
|
||||
notes: { type: string, nullable: true }
|
||||
done: { type: boolean }
|
||||
dueAt: { type: string, format: date-time, nullable: true }
|
||||
responses:
|
||||
"200": { description: Updated list with items }
|
||||
delete:
|
||||
summary: Delete item
|
||||
tags: [Lists]
|
||||
responses:
|
||||
"200": { description: Updated list with items }
|
||||
|
||||
/api/v1/notes:
|
||||
get:
|
||||
summary: List notes
|
||||
tags: [Notes]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/Note" }
|
||||
post:
|
||||
summary: Create note
|
||||
tags: [Notes]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/NoteInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Note" }
|
||||
|
||||
/api/v1/notes/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: Get note
|
||||
tags: [Notes]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Note" }
|
||||
patch:
|
||||
summary: Update note
|
||||
tags: [Notes]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string }
|
||||
body: { type: string }
|
||||
pinned: { type: boolean }
|
||||
remindAt: { type: string, format: date-time, nullable: true }
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Note" }
|
||||
delete:
|
||||
summary: Delete note
|
||||
tags: [Notes]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
/api/v1/garden/containers:
|
||||
get:
|
||||
summary: List garden containers
|
||||
tags: [Garden]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/GardenContainer" }
|
||||
post:
|
||||
summary: Create container
|
||||
tags: [Garden]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/ContainerInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/GardenContainerDetail" }
|
||||
|
||||
/api/v1/garden/containers/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: Get container with plants
|
||||
tags: [Garden]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/GardenContainerDetail" }
|
||||
"404": { description: Not found }
|
||||
patch:
|
||||
summary: Update container
|
||||
tags: [Garden]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ContainerInput"
|
||||
description: All fields optional
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/GardenContainerDetail" }
|
||||
delete:
|
||||
summary: Delete container
|
||||
tags: [Garden]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
/api/v1/garden/plants:
|
||||
get:
|
||||
summary: List plants
|
||||
tags: [Garden]
|
||||
parameters:
|
||||
- name: containerId
|
||||
in: query
|
||||
schema: { type: string, format: uuid }
|
||||
description: Filter by container
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/PlantListItem" }
|
||||
post:
|
||||
summary: Create plant
|
||||
tags: [Garden]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/PlantInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/PlantDetail" }
|
||||
|
||||
/api/v1/garden/plants/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
summary: Get plant detail
|
||||
tags: [Garden]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/PlantDetail" }
|
||||
"404": { description: Not found }
|
||||
patch:
|
||||
summary: Update plant
|
||||
tags: [Garden]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PlantInput"
|
||||
description: All fields optional
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/PlantDetail" }
|
||||
delete:
|
||||
summary: Delete plant
|
||||
tags: [Garden]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
/api/v1/bangs:
|
||||
get:
|
||||
summary: Bang stats (total + recent)
|
||||
tags: [Bangs]
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
schema: { type: integer, default: 10, minimum: 1, maximum: 100 }
|
||||
description: Max recent entries to return
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/BangStats" }
|
||||
post:
|
||||
summary: Record a bang
|
||||
tags: [Bangs]
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/BangInput" }
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Bang" }
|
||||
|
||||
/api/v1/bangs/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
patch:
|
||||
summary: Update bang date
|
||||
tags: [Bangs]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/BangUpdateInput" }
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Bang" }
|
||||
delete:
|
||||
summary: Delete bang
|
||||
tags: [Bangs]
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/OkResponse" }
|
||||
|
||||
tags:
|
||||
- name: Calendars
|
||||
- name: Events
|
||||
- name: Lists
|
||||
- name: Notes
|
||||
- name: Garden
|
||||
- name: Bangs
|
||||
Reference in New Issue
Block a user