From d3e6aa199587df1b60b53802b8c0f72bc3acbbfd Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 5 Dec 2022 12:05:32 -0500 Subject: [PATCH 01/11] Start using OpenAPI --- components/api/EnumTable.tsx | 26 + components/api/ObjectDescription.tsx | 24 +- components/api/PathTable.tsx | 13 + components/api/ResponsesTable.tsx | 24 +- lib/enums.json | 53 + lib/fixtures.json | 30 + lib/openapi.json | 2056 +++++++++++++++++ lib/openapi.ts | 1057 +++++++++ pages/api-reference/exports/index.mdx | 84 +- pages/api-reference/exports/metadata.json | 9 +- pages/api-reference/subscribers/index.mdx | 134 +- pages/api-reference/subscribers/metadata.json | 19 +- 12 files changed, 3320 insertions(+), 209 deletions(-) create mode 100644 components/api/EnumTable.tsx create mode 100644 components/api/PathTable.tsx create mode 100644 lib/enums.json create mode 100644 lib/fixtures.json create mode 100644 lib/openapi.json create mode 100644 lib/openapi.ts diff --git a/components/api/EnumTable.tsx b/components/api/EnumTable.tsx new file mode 100644 index 0000000..0279721 --- /dev/null +++ b/components/api/EnumTable.tsx @@ -0,0 +1,26 @@ +import Table from "../Table"; +import Pill from "../Pill"; + +export default function EnumTable({ e }: any) { + return ( + ( + {row.name || row.type} + ), + }, + { + title: "identifier", + component: (s) => {s.type}, + }, + { title: "description" }, + ]} + content={Object.keys(e).map((key) => ({ + type: key, + ...e[key], + }))} + /> + ); +} diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index 3fbb208..5d61fe4 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -1,5 +1,6 @@ import { Code } from "../Markdown"; import Table, { Row } from "../Table"; +import OpenAPIEnum from "../../lib/enums.json"; function MonospacedSpan(s: string) { return {s}; @@ -16,6 +17,18 @@ type Props = { fields: Array; }; +const extractRefFromType = (type: string) => { + // If #/components/schemas/ is present, extract the name of the schema + const match = type.match(/#\/components\/schemas\/(.*)/); + if (match) { + const ref = match[1]; + if (OpenAPIEnum[ref] !== undefined) { + return ref; + } + } + return null; +}; + export default function ObjectDescription({ example, fields }: Props) { return (
@@ -31,7 +44,16 @@ export default function ObjectDescription({ example, fields }: Props) { }, { title: "type", - component: (c: Field) => MonospacedSpan(c.type), + component: (c: Field) => + MonospacedSpan( + extractRefFromType(c.type) ? ( + + {extractRefFromType(c.type)} + + ) : ( + c.type + ) + ), }, { title: "description" }, ]} diff --git a/components/api/PathTable.tsx b/components/api/PathTable.tsx new file mode 100644 index 0000000..690d868 --- /dev/null +++ b/components/api/PathTable.tsx @@ -0,0 +1,13 @@ +import ResponsesTable from "./ResponsesTable"; + +export default function PathTable({ content }: any) { + return ( + ({ + Status: key, + description: content[key].description, + "Sample Response": content[key].content["application/json"].schema, + }))} + /> + ); +} diff --git a/components/api/ResponsesTable.tsx b/components/api/ResponsesTable.tsx index db07276..741df01 100644 --- a/components/api/ResponsesTable.tsx +++ b/components/api/ResponsesTable.tsx @@ -1,6 +1,7 @@ import classNames from "../../lib/classNames"; import { Code, H3 } from "../Markdown"; import Table, { Row } from "../Table"; +import OpenAPIFixtures from "../../lib/fixtures.json"; function ResponseCodeBadge(text: string) { return ( @@ -19,12 +20,25 @@ function ResponseCodeBadge(text: string) { ); } +const fixtureForRef = (ref: string) => { + // Pages are wrapped like so: "Page_FOO_". + // We want to extract 'FOO'. + if (ref.startsWith("Page_")) { + const pageName = ref.split("_")[1]; + const pageRef = OpenAPIFixtures[pageName]; + return { + results: [pageRef], + count: 1, + }; + } + return OpenAPIFixtures[ref] || ref; +}; + function SampleResponse(text: any) { - return ( - - {JSON.stringify(text["Sample Response"], null, 4)} - - ); + const relevantText = text["Sample Response"]["$ref"] + ? fixtureForRef(text["Sample Response"]["$ref"].split("/").pop()) + : text["Sample Response"]; + return {JSON.stringify(relevantText, null, 4)}; } type Response = { diff --git a/lib/enums.json b/lib/enums.json new file mode 100644 index 0000000..f68c099 --- /dev/null +++ b/lib/enums.json @@ -0,0 +1,53 @@ +{ + "SubscriberType": { + "regular": { + "variant": "success", + "description": "normal subscribers who have not unsubscribed or deactivated in any way" + }, + "premium": { + "variant": "success", + "description": "subscribers with active premium subscriptions" + }, + "trialed": { + "variant": "info", + "description": "subscribers that are temporarily receiving a premium subscription to your newsletter" + }, + "unpaid": { + "variant": "info", + "description": "subscribers who have not yet purchased a subscription to your newsletter" + }, + "gifted": { + "variant": "info", + "description": "subscribers that have been gifted free premium subscriptions" + }, + "churning": { + "variant": "warning", + "description": "subscribers who have elected to not renew their subscription to your newsletter and will become unpaid subscribers at the end of their current billing period" + }, + "unactivated": { + "variant": "warning", + "description": "subscribers who have not yet confirmed their email or opted in" + }, + "paused": { + "variant": "warning", + "description": "subscribers that are on a temporary hold from their premium subscription, but are still subscribed to your newsletter" + }, + "unsubscribed": { + "variant": "error", + "description": "subscribers that have voluntarily unsubscribed from your newsletter" + }, + "spammy": { + "variant": "error", + "description": "subscribers that have been deemed spammy by Buttondown's automated systems" + }, + "removed": { + "variant": "error", + "description": "subscribers who have been explicitly removed by the newsletter (notably, this does not mean unsubscribers: use /v1/unsubscribers for that!)" + }, + "past_due": { + "variant": "error", + "name": "Past due", + "description": "subscribers who technically have active paid subscriptions, but have not paid their invoices in time" + } + } +} diff --git a/lib/fixtures.json b/lib/fixtures.json new file mode 100644 index 0000000..fe58c38 --- /dev/null +++ b/lib/fixtures.json @@ -0,0 +1,30 @@ +{ + "ExportOutput": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T15:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "url": "s3://buttondown/path-to-export", + "status": "completed" + }, + "ErrorMessage": { + "code": "something_went_wrong", + "detail": "Your call is very important to us." + }, + "SubscriberOutput": { + "creation_date": "2019-08-24T14:15:22Z", + "email": "user@example.com", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "metadata": { + "foo": "bar" + }, + "notes": "", + "referrer_url": "http://jmduke.com/", + "secondary_id": 3, + "source": "api", + "subscriber_type": "regular", + "tags": [], + "utm_campaign": "", + "utm_medium": "", + "utm_source": "" + } +} diff --git a/lib/openapi.json b/lib/openapi.json new file mode 100644 index 0000000..25f77f5 --- /dev/null +++ b/lib/openapi.json @@ -0,0 +1,2056 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "NinjaAPI", + "version": "1.0.0", + "description": "" + }, + "paths": { + "/exports": { + "post": { + "operationId": "api_views_exports_create_export", + "summary": "Create Export", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExportOutput" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Payload", + "default": { + "collections": [ + "subscribers", + "emails", + "scheduled_emails", + "drafts", + "unsubscribers", + "events", + "referrals" + ] + }, + "allOf": [ + { + "$ref": "#/components/schemas/ExportInput" + } + ] + } + } + }, + "required": false + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "get": { + "operationId": "api_views_exports_list_exports", + "summary": "List Exports", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_ExportOutput_" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/exports/{export_id}": { + "get": { + "operationId": "api_views_exports_retrieve_export", + "summary": "Retrieve Export", + "parameters": [ + { + "in": "path", + "name": "export_id", + "schema": { + "title": "Export Id", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExportOutput" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/tags": { + "post": { + "operationId": "api_views_subscriber_tags_create_tag", + "summary": "Create Tag", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "get": { + "operationId": "api_views_subscriber_tags_list_tags", + "summary": "List Tags", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_TagOutput_" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/tags/{tag_id}": { + "get": { + "operationId": "api_views_subscriber_tags_retrieve_tag", + "summary": "Retrieve Tag", + "parameters": [ + { + "in": "path", + "name": "tag_id", + "schema": { + "title": "Tag Id", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagOutput" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "patch": { + "operationId": "api_views_subscriber_tags_update_tag", + "summary": "Update Tag", + "parameters": [ + { + "in": "path", + "name": "tag_id", + "schema": { + "title": "Tag Id", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage_UpdateTagErrorCode_" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagUpdateInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "delete": { + "operationId": "api_views_subscriber_tags_delete_tag", + "summary": "Delete Tag", + "parameters": [ + { + "in": "path", + "name": "tag_id", + "schema": { + "title": "Tag Id", + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/ping": { + "get": { + "operationId": "api_views_ping_ping", + "summary": "Ping", + "parameters": [], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/images": { + "post": { + "operationId": "api_views_images_create_image", + "summary": "Create Image", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageOutput" + } + } + } + } + }, + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "title": "FileParams", + "type": "object", + "properties": { + "image": { + "title": "Image", + "type": "string", + "format": "binary" + } + }, + "required": [ + "image" + ] + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/images/{image_id}": { + "delete": { + "operationId": "api_views_images_delete_image", + "summary": "Delete Image", + "parameters": [ + { + "in": "path", + "name": "image_id", + "schema": { + "title": "Image Id", + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/emails": { + "post": { + "operationId": "api_views_emails_create_email", + "summary": "Create Email", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage_EmailCreationErrorCode_" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "get": { + "operationId": "api_views_emails_list_emails", + "summary": "List Emails", + "parameters": [ + { + "in": "query", + "name": "status", + "schema": { + "default": [ + "about_to_send", + "in_flight", + "sent" + ], + "type": "array", + "items": { + "$ref": "#/components/schemas/EmailStatus" + } + }, + "required": false + }, + { + "in": "query", + "name": "included_tags", + "schema": { + "title": "Included Tags", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "required": false + }, + { + "in": "query", + "name": "excluded_tags", + "schema": { + "title": "Excluded Tags", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "required": false + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_EmailOutput_" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/emails/{pk}": { + "get": { + "operationId": "api_views_emails_retrieve_email", + "summary": "Retrieve Email", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailOutput" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/emails/{pk}/analytics": { + "get": { + "operationId": "api_views_emails_retrieve_email_analytics", + "summary": "Retrieve Email Analytics", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Analytics" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/subscribers": { + "post": { + "operationId": "api_views_subscribers_create_subscriber", + "summary": "Create Subscriber", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriberOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriberInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "get": { + "operationId": "api_views_subscribers_list_subscribers", + "summary": "List Subscribers", + "parameters": [ + { + "in": "query", + "name": "type", + "schema": { + "title": "Type", + "description": "An enumeration.", + "enum": [ + "regular", + "premium", + "churning", + "past_due", + "gifted", + "unpaid", + "unactivated", + "unsubscribed", + "spammy", + "removed", + "trialed", + "disabled", + "paused" + ], + "type": "string" + }, + "required": false, + "description": "An enumeration." + }, + { + "in": "query", + "name": "email", + "schema": { + "title": "Email", + "default": "", + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "tag", + "schema": { + "title": "Tag", + "default": "", + "type": "string" + }, + "required": false + }, + { + "in": "query", + "name": "-tag", + "schema": { + "title": "-Tag", + "type": "string" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_SubscriberOutput_" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage_ListSubscribersErrorCode_" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/subscribers/{pk}": { + "get": { + "operationId": "api_views_subscribers_retrieve_subscriber", + "summary": "Retrieve Subscriber", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriberOutput" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "delete": { + "operationId": "api_views_subscribers_delete_subscriber", + "summary": "Delete Subscriber", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "patch": { + "operationId": "api_views_subscribers_update_subscriber", + "summary": "Update Subscriber", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriberOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage_UpdateSubscriberErrorCode_" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscriberUpdateInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/subscribers/{pk}/send-reminder": { + "post": { + "operationId": "api_views_subscribers_send_reminder", + "summary": "Send Reminder", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/subscribers/{pk}/emails/{email_pk}": { + "post": { + "operationId": "api_views_subscribers_send_email_to", + "summary": "Send Email To", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + }, + { + "in": "path", + "name": "email_pk", + "schema": { + "title": "Email Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/newsletters": { + "get": { + "operationId": "api_views_newsletters_list_newsletters", + "summary": "List Newsletters", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_NewsletterOutput_" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "post": { + "operationId": "api_views_newsletters_create_newsletter", + "summary": "Create Newsletter", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsletterOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage_CreateNewsletterErrorCode_" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsletterInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/newsletters/{pk}": { + "patch": { + "operationId": "api_views_newsletters_update_newsletter", + "summary": "Update Newsletter", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsletterOutput" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsletterUpdateInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + }, + "delete": { + "operationId": "api_views_newsletters_delete_newsletter", + "summary": "Delete Newsletter", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + } + }, + "components": { + "schemas": { + "ExportStatus": { + "title": "Status", + "description": "An enumeration.", + "enum": [ + "error", + "in_progress", + "not_started", + "ready" + ], + "type": "string" + }, + "ExportOutput": { + "title": "ExportOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "creation_date": { + "title": "Creation Date", + "type": "string", + "format": "date-time" + }, + "status": { + "$ref": "#/components/schemas/ExportStatus" + }, + "url": { + "title": "Url", + "type": "string" + }, + "completion_date": { + "title": "Completion Date", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "creation_date", + "status", + "url" + ] + }, + "ErrorMessage": { + "title": "ErrorMessage", + "type": "object", + "properties": { + "code": { + "title": "Code" + }, + "detail": { + "title": "Detail", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "detail" + ] + }, + "ExportCollection": { + "title": "Collection", + "description": "An enumeration.", + "enum": [ + "subscribers", + "emails", + "scheduled_emails", + "drafts", + "unsubscribers", + "events", + "referrals" + ], + "type": "string" + }, + "ExportInput": { + "title": "ExportInput", + "type": "object", + "properties": { + "collections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExportCollection" + } + } + }, + "required": [ + "collections" + ] + }, + "Page_ExportOutput_": { + "title": "Page[ExportOutput]", + "type": "object", + "properties": { + "results": { + "title": "Results", + "type": "array", + "items": { + "$ref": "#/components/schemas/ExportOutput" + } + }, + "count": { + "title": "Count", + "type": "integer" + } + }, + "required": [ + "results", + "count" + ] + }, + "TagOutput": { + "title": "TagOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "name": { + "title": "Name", + "type": "string" + }, + "color": { + "title": "Color", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "secondary_id": { + "title": "Secondary Id", + "type": "integer" + }, + "creation_date": { + "title": "Creation Date", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "name", + "color", + "secondary_id", + "creation_date" + ] + }, + "TagInput": { + "title": "TagInput", + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "color": { + "title": "Color", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + } + }, + "required": [ + "name", + "color" + ] + }, + "Page_TagOutput_": { + "title": "Page[TagOutput]", + "type": "object", + "properties": { + "results": { + "title": "Results", + "type": "array", + "items": { + "$ref": "#/components/schemas/TagOutput" + } + }, + "count": { + "title": "Count", + "type": "integer" + } + }, + "required": [ + "results", + "count" + ] + }, + "UpdateTagErrorCode": { + "title": "UpdateTagErrorCode", + "description": "An enumeration.", + "enum": [ + "name_already_exists" + ], + "type": "string" + }, + "ErrorMessage_UpdateTagErrorCode_": { + "title": "ErrorMessage[UpdateTagErrorCode]", + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/UpdateTagErrorCode" + }, + "detail": { + "title": "Detail", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "code", + "detail" + ] + }, + "TagUpdateInput": { + "title": "TagUpdateInput", + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "color": { + "title": "Color", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + } + } + }, + "ImageOutput": { + "title": "ImageOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "creation_date": { + "title": "Creation Date", + "type": "string", + "format": "date-time" + }, + "image": { + "title": "Image", + "type": "string" + } + }, + "required": [ + "id", + "creation_date", + "image" + ] + }, + "EmailType": { + "title": "Type", + "description": "An enumeration.", + "enum": [ + "public", + "private", + "premium", + "free", + "archival", + "hidden" + ], + "type": "string" + }, + "EmailStatus": { + "title": "Status", + "description": "An enumeration.", + "enum": [ + "draft", + "about_to_send", + "scheduled", + "in_flight", + "deleted", + "errored", + "sent", + "imported" + ], + "type": "string" + }, + "EmailOutput": { + "title": "EmailOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "included_tags": { + "title": "Included Tags", + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "excluded_tags": { + "title": "Excluded Tags", + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "publish_date": { + "title": "Publish Date", + "type": "string", + "format": "date-time" + }, + "subject": { + "title": "Subject", + "type": "string" + }, + "body": { + "title": "Body", + "type": "string" + }, + "secondary_id": { + "title": "Secondary Id", + "type": "integer" + }, + "email_type": { + "$ref": "#/components/schemas/EmailType" + }, + "slug": { + "title": "Slug", + "type": "string" + }, + "external_url": { + "title": "External Url", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/EmailStatus" + }, + "metadata": { + "title": "Metadata", + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "id", + "subject", + "body", + "email_type", + "slug", + "external_url", + "status" + ] + }, + "EmailCreationErrorCode": { + "title": "EmailCreationErrorCode", + "description": "An enumeration.", + "enum": [ + "subject_invalid", + "email_duplicate", + "email_invalid" + ], + "type": "string" + }, + "ErrorMessage_EmailCreationErrorCode_": { + "title": "ErrorMessage[EmailCreationErrorCode]", + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/EmailCreationErrorCode" + }, + "detail": { + "title": "Detail", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "code", + "detail" + ] + }, + "EmailInput": { + "title": "EmailInput", + "type": "object", + "properties": { + "included_tags": { + "title": "Included Tags", + "default": [], + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string" + } + ] + } + }, + "excluded_tags": { + "title": "Excluded Tags", + "default": [], + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string" + } + ] + } + }, + "publish_date": { + "title": "Publish Date", + "type": "string", + "format": "date-time" + }, + "subject": { + "title": "Subject", + "type": "string" + }, + "body": { + "title": "Body", + "default": "", + "type": "string" + }, + "email_type": { + "default": "public", + "allOf": [ + { + "$ref": "#/components/schemas/EmailType" + } + ] + }, + "status": { + "default": "about_to_send", + "allOf": [ + { + "$ref": "#/components/schemas/EmailStatus" + } + ] + }, + "metadata": { + "title": "Metadata", + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "subject" + ] + }, + "Page_EmailOutput_": { + "title": "Page[EmailOutput]", + "type": "object", + "properties": { + "results": { + "title": "Results", + "type": "array", + "items": { + "$ref": "#/components/schemas/EmailOutput" + } + }, + "count": { + "title": "Count", + "type": "integer" + } + }, + "required": [ + "results", + "count" + ] + }, + "Analytics": { + "title": "Analytics", + "type": "object", + "properties": { + "recipients": { + "title": "Recipients", + "type": "integer" + }, + "deliveries": { + "title": "Deliveries", + "type": "integer" + }, + "opens": { + "title": "Opens", + "type": "integer" + }, + "clicks": { + "title": "Clicks", + "type": "integer" + }, + "temporary_failures": { + "title": "Temporary Failures", + "type": "integer" + }, + "permanent_failures": { + "title": "Permanent Failures", + "type": "integer" + }, + "unsubscriptions": { + "title": "Unsubscriptions", + "type": "integer" + }, + "complaints": { + "title": "Complaints", + "type": "integer" + } + }, + "required": [ + "recipients", + "deliveries", + "opens", + "clicks", + "temporary_failures", + "permanent_failures", + "unsubscriptions", + "complaints" + ] + }, + "SubscriberType": { + "title": "Type", + "description": "An enumeration.", + "enum": [ + "regular", + "premium", + "churning", + "past_due", + "gifted", + "unpaid", + "unactivated", + "unsubscribed", + "spammy", + "removed", + "trialed", + "disabled", + "paused" + ], + "type": "string" + }, + "SubscriberSource": { + "title": "Source", + "description": "An enumeration.", + "enum": [ + "api", + "buttondown", + "csv", + "mailchimp", + "organic", + "nouveau", + "substack", + "tinyletter", + "typeform", + "user", + "admin", + "drip" + ], + "type": "string" + }, + "SubscriberOutput": { + "title": "SubscriberOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "email": { + "title": "Email", + "type": "string" + }, + "notes": { + "title": "Notes", + "default": "", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "default": {}, + "type": "object" + }, + "tags": { + "title": "Tags", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "referrer_url": { + "title": "Referrer Url", + "default": "", + "type": "string" + }, + "creation_date": { + "title": "Creation Date", + "type": "string", + "format": "date-time" + }, + "secondary_id": { + "title": "Secondary Id", + "type": "integer" + }, + "subscriber_type": { + "$ref": "#/components/schemas/SubscriberType" + }, + "source": { + "$ref": "#/components/schemas/SubscriberSource" + }, + "utm_campaign": { + "title": "Utm Campaign", + "type": "string" + }, + "utm_medium": { + "title": "Utm Medium", + "type": "string" + }, + "utm_source": { + "title": "Utm Source", + "type": "string" + } + }, + "required": [ + "id", + "email", + "creation_date", + "secondary_id", + "subscriber_type", + "source", + "utm_campaign", + "utm_medium", + "utm_source" + ] + }, + "SubscriberInput": { + "title": "SubscriberInput", + "type": "object", + "properties": { + "email": { + "title": "Email", + "type": "string" + }, + "notes": { + "title": "Notes", + "default": "", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "default": {}, + "type": "object" + }, + "tags": { + "title": "Tags", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "referrer_url": { + "title": "Referrer Url", + "default": "", + "type": "string" + } + }, + "required": [ + "email" + ] + }, + "Page_SubscriberOutput_": { + "title": "Page[SubscriberOutput]", + "type": "object", + "properties": { + "results": { + "title": "Results", + "type": "array", + "items": { + "$ref": "#/components/schemas/SubscriberOutput" + } + }, + "count": { + "title": "Count", + "type": "integer" + } + }, + "required": [ + "results", + "count" + ] + }, + "ListSubscribersErrorCode": { + "title": "ListSubscribersErrorCode", + "description": "An enumeration.", + "enum": [ + "invalid_tag" + ], + "type": "string" + }, + "ErrorMessage_ListSubscribersErrorCode_": { + "title": "ErrorMessage[ListSubscribersErrorCode]", + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/ListSubscribersErrorCode" + }, + "detail": { + "title": "Detail", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "code", + "detail" + ] + }, + "UpdateSubscriberErrorCode": { + "title": "UpdateSubscriberErrorCode", + "description": "An enumeration.", + "enum": [ + "email_already_exists", + "email_invalid", + "subscriber_type_invalid" + ], + "type": "string" + }, + "ErrorMessage_UpdateSubscriberErrorCode_": { + "title": "ErrorMessage[UpdateSubscriberErrorCode]", + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/UpdateSubscriberErrorCode" + }, + "detail": { + "title": "Detail", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "code", + "detail" + ] + }, + "SubscriberUpdateInput": { + "title": "SubscriberUpdateInput", + "type": "object", + "properties": { + "email": { + "title": "Email", + "type": "string" + }, + "notes": { + "title": "Notes", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object" + }, + "tags": { + "title": "Tags", + "type": "array", + "items": { + "type": "string" + } + }, + "referrer_url": { + "title": "Referrer Url", + "default": "", + "type": "string" + }, + "subscriber_type": { + "$ref": "#/components/schemas/SubscriberType" + } + } + }, + "NewsletterOutput": { + "title": "NewsletterOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "username": { + "title": "Username", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + }, + "creation_date": { + "title": "Creation Date", + "type": "string", + "format": "date-time" + }, + "api_key": { + "title": "Api Key", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "id", + "username", + "name", + "description", + "creation_date", + "api_key" + ] + }, + "Page_NewsletterOutput_": { + "title": "Page[NewsletterOutput]", + "type": "object", + "properties": { + "results": { + "title": "Results", + "type": "array", + "items": { + "$ref": "#/components/schemas/NewsletterOutput" + } + }, + "count": { + "title": "Count", + "type": "integer" + } + }, + "required": [ + "results", + "count" + ] + }, + "CreateNewsletterErrorCode": { + "title": "CreateNewsletterErrorCode", + "description": "An enumeration.", + "enum": [ + "username_already_exists" + ], + "type": "string" + }, + "ErrorMessage_CreateNewsletterErrorCode_": { + "title": "ErrorMessage[CreateNewsletterErrorCode]", + "type": "object", + "properties": { + "code": { + "$ref": "#/components/schemas/CreateNewsletterErrorCode" + }, + "detail": { + "title": "Detail", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "code", + "detail" + ] + }, + "NewsletterInput": { + "title": "NewsletterInput", + "type": "object", + "properties": { + "username": { + "title": "Username", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + } + }, + "required": [ + "username", + "name", + "description" + ] + }, + "NewsletterUpdateInput": { + "title": "NewsletterUpdateInput", + "type": "object", + "properties": { + "username": { + "title": "Username", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string" + } + } + } + }, + "securitySchemes": { + "GlobalAuth": { + "type": "http", + "scheme": "token" + } + } + } +} \ No newline at end of file diff --git a/lib/openapi.ts b/lib/openapi.ts new file mode 100644 index 0000000..3500af1 --- /dev/null +++ b/lib/openapi.ts @@ -0,0 +1,1057 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + + +export interface paths { + "/exports": { + /** List Exports */ + get: operations["api_views_exports_list_exports"]; + /** Create Export */ + post: operations["api_views_exports_create_export"]; + }; + "/exports/{export_id}": { + /** Retrieve Export */ + get: operations["api_views_exports_retrieve_export"]; + }; + "/tags": { + /** List Tags */ + get: operations["api_views_subscriber_tags_list_tags"]; + /** Create Tag */ + post: operations["api_views_subscriber_tags_create_tag"]; + }; + "/tags/{tag_id}": { + /** Retrieve Tag */ + get: operations["api_views_subscriber_tags_retrieve_tag"]; + /** Delete Tag */ + delete: operations["api_views_subscriber_tags_delete_tag"]; + /** Update Tag */ + patch: operations["api_views_subscriber_tags_update_tag"]; + }; + "/ping": { + /** Ping */ + get: operations["api_views_ping_ping"]; + }; + "/images": { + /** Create Image */ + post: operations["api_views_images_create_image"]; + }; + "/images/{image_id}": { + /** Delete Image */ + delete: operations["api_views_images_delete_image"]; + }; + "/emails": { + /** List Emails */ + get: operations["api_views_emails_list_emails"]; + /** Create Email */ + post: operations["api_views_emails_create_email"]; + }; + "/emails/{pk}": { + /** Retrieve Email */ + get: operations["api_views_emails_retrieve_email"]; + }; + "/emails/{pk}/analytics": { + /** Retrieve Email Analytics */ + get: operations["api_views_emails_retrieve_email_analytics"]; + }; + "/subscribers": { + /** List Subscribers */ + get: operations["api_views_subscribers_list_subscribers"]; + /** Create Subscriber */ + post: operations["api_views_subscribers_create_subscriber"]; + }; + "/subscribers/{pk}": { + /** Retrieve Subscriber */ + get: operations["api_views_subscribers_retrieve_subscriber"]; + /** Delete Subscriber */ + delete: operations["api_views_subscribers_delete_subscriber"]; + /** Update Subscriber */ + patch: operations["api_views_subscribers_update_subscriber"]; + }; + "/subscribers/{pk}/send-reminder": { + /** Send Reminder */ + post: operations["api_views_subscribers_send_reminder"]; + }; + "/subscribers/{pk}/emails/{email_pk}": { + /** Send Email To */ + post: operations["api_views_subscribers_send_email_to"]; + }; + "/newsletters": { + /** List Newsletters */ + get: operations["api_views_newsletters_list_newsletters"]; + /** Create Newsletter */ + post: operations["api_views_newsletters_create_newsletter"]; + }; + "/newsletters/{pk}": { + /** Delete Newsletter */ + delete: operations["api_views_newsletters_delete_newsletter"]; + /** Update Newsletter */ + patch: operations["api_views_newsletters_update_newsletter"]; + }; +} + +export type webhooks = Record; + +export interface components { + schemas: { + /** + * Status + * @description An enumeration. + * @enum {string} + */ + ExportStatus: "error" | "in_progress" | "not_started" | "ready"; + /** ExportOutput */ + ExportOutput: { + /** + * Id + * Format: uuid + */ + id: string; + /** + * Creation Date + * Format: date-time + */ + creation_date: string; + status: components["schemas"]["ExportStatus"]; + /** Url */ + url: string; + /** + * Completion Date + * Format: date-time + */ + completion_date?: string; + }; + /** ErrorMessage */ + ErrorMessage: { + /** Code */ + code?: Record; + /** Detail */ + detail: string; + /** Metadata */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** + * Collection + * @description An enumeration. + * @enum {string} + */ + ExportCollection: "subscribers" | "emails" | "scheduled_emails" | "drafts" | "unsubscribers" | "events" | "referrals"; + /** ExportInput */ + ExportInput: { + collections: (components["schemas"]["ExportCollection"])[]; + }; + /** Page[ExportOutput] */ + Page_ExportOutput_: { + /** Results */ + results: (components["schemas"]["ExportOutput"])[]; + /** Count */ + count: number; + }; + /** TagOutput */ + TagOutput: { + /** + * Id + * Format: uuid + */ + id: string; + /** Name */ + name: string; + /** Color */ + color: string; + /** Description */ + description?: string; + /** Secondary Id */ + secondary_id: number; + /** + * Creation Date + * Format: date-time + */ + creation_date: string; + }; + /** TagInput */ + TagInput: { + /** Name */ + name: string; + /** Color */ + color: string; + /** Description */ + description?: string; + }; + /** Page[TagOutput] */ + Page_TagOutput_: { + /** Results */ + results: (components["schemas"]["TagOutput"])[]; + /** Count */ + count: number; + }; + /** + * UpdateTagErrorCode + * @description An enumeration. + * @enum {string} + */ + UpdateTagErrorCode: "name_already_exists"; + /** ErrorMessage[UpdateTagErrorCode] */ + ErrorMessage_UpdateTagErrorCode_: { + code: components["schemas"]["UpdateTagErrorCode"]; + /** Detail */ + detail: string; + /** Metadata */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** TagUpdateInput */ + TagUpdateInput: { + /** Name */ + name?: string; + /** Color */ + color?: string; + /** Description */ + description?: string; + }; + /** ImageOutput */ + ImageOutput: { + /** + * Id + * Format: uuid + */ + id: string; + /** + * Creation Date + * Format: date-time + */ + creation_date: string; + /** Image */ + image: string; + }; + /** + * Type + * @description An enumeration. + * @enum {string} + */ + EmailType: "public" | "private" | "premium" | "free" | "archival" | "hidden"; + /** + * Status + * @description An enumeration. + * @enum {string} + */ + EmailStatus: "draft" | "about_to_send" | "scheduled" | "in_flight" | "deleted" | "errored" | "sent" | "imported"; + /** EmailOutput */ + EmailOutput: { + /** + * Id + * Format: uuid + */ + id: string; + /** Included Tags */ + included_tags?: (string)[]; + /** Excluded Tags */ + excluded_tags?: (string)[]; + /** + * Publish Date + * Format: date-time + */ + publish_date?: string; + /** Subject */ + subject: string; + /** Body */ + body: string; + /** Secondary Id */ + secondary_id?: number; + email_type: components["schemas"]["EmailType"]; + /** Slug */ + slug: string; + /** External Url */ + external_url: string; + status: components["schemas"]["EmailStatus"]; + /** + * Metadata + * @default {} + */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** + * EmailCreationErrorCode + * @description An enumeration. + * @enum {string} + */ + EmailCreationErrorCode: "subject_invalid" | "email_duplicate" | "email_invalid"; + /** ErrorMessage[EmailCreationErrorCode] */ + ErrorMessage_EmailCreationErrorCode_: { + code: components["schemas"]["EmailCreationErrorCode"]; + /** Detail */ + detail: string; + /** Metadata */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** EmailInput */ + EmailInput: { + /** + * Included Tags + * @default [] + */ + included_tags?: (string | string)[]; + /** + * Excluded Tags + * @default [] + */ + excluded_tags?: (string | string)[]; + /** + * Publish Date + * Format: date-time + */ + publish_date?: string; + /** Subject */ + subject: string; + /** + * Body + * @default + */ + body?: string; + /** @default public */ + email_type?: components["schemas"]["EmailType"]; + /** @default about_to_send */ + status?: components["schemas"]["EmailStatus"]; + /** + * Metadata + * @default {} + */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** Page[EmailOutput] */ + Page_EmailOutput_: { + /** Results */ + results: (components["schemas"]["EmailOutput"])[]; + /** Count */ + count: number; + }; + /** Analytics */ + Analytics: { + /** Recipients */ + recipients: number; + /** Deliveries */ + deliveries: number; + /** Opens */ + opens: number; + /** Clicks */ + clicks: number; + /** Temporary Failures */ + temporary_failures: number; + /** Permanent Failures */ + permanent_failures: number; + /** Unsubscriptions */ + unsubscriptions: number; + /** Complaints */ + complaints: number; + }; + /** + * Type + * @description An enumeration. + * @enum {string} + */ + SubscriberType: "regular" | "premium" | "churning" | "past_due" | "gifted" | "unpaid" | "unactivated" | "unsubscribed" | "spammy" | "removed" | "trialed" | "disabled" | "paused"; + /** + * Source + * @description An enumeration. + * @enum {string} + */ + SubscriberSource: "api" | "buttondown" | "csv" | "mailchimp" | "organic" | "nouveau" | "substack" | "tinyletter" | "typeform" | "user" | "admin" | "drip"; + /** SubscriberOutput */ + SubscriberOutput: { + /** + * Id + * Format: uuid + */ + id: string; + /** Email */ + email: string; + /** + * Notes + * @default + */ + notes?: string; + /** + * Metadata + * @default {} + */ + metadata?: Record; + /** + * Tags + * @default [] + */ + tags?: (string)[]; + /** + * Referrer Url + * @default + */ + referrer_url?: string; + /** + * Creation Date + * Format: date-time + */ + creation_date: string; + /** Secondary Id */ + secondary_id: number; + subscriber_type: components["schemas"]["SubscriberType"]; + source: components["schemas"]["SubscriberSource"]; + /** Utm Campaign */ + utm_campaign: string; + /** Utm Medium */ + utm_medium: string; + /** Utm Source */ + utm_source: string; + }; + /** SubscriberInput */ + SubscriberInput: { + /** Email */ + email: string; + /** + * Notes + * @default + */ + notes?: string; + /** + * Metadata + * @default {} + */ + metadata?: Record; + /** + * Tags + * @default [] + */ + tags?: (string)[]; + /** + * Referrer Url + * @default + */ + referrer_url?: string; + }; + /** Page[SubscriberOutput] */ + Page_SubscriberOutput_: { + /** Results */ + results: (components["schemas"]["SubscriberOutput"])[]; + /** Count */ + count: number; + }; + /** + * ListSubscribersErrorCode + * @description An enumeration. + * @enum {string} + */ + ListSubscribersErrorCode: "invalid_tag"; + /** ErrorMessage[ListSubscribersErrorCode] */ + ErrorMessage_ListSubscribersErrorCode_: { + code: components["schemas"]["ListSubscribersErrorCode"]; + /** Detail */ + detail: string; + /** Metadata */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** + * UpdateSubscriberErrorCode + * @description An enumeration. + * @enum {string} + */ + UpdateSubscriberErrorCode: "email_already_exists" | "email_invalid" | "subscriber_type_invalid"; + /** ErrorMessage[UpdateSubscriberErrorCode] */ + ErrorMessage_UpdateSubscriberErrorCode_: { + code: components["schemas"]["UpdateSubscriberErrorCode"]; + /** Detail */ + detail: string; + /** Metadata */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** SubscriberUpdateInput */ + SubscriberUpdateInput: { + /** Email */ + email?: string; + /** Notes */ + notes?: string; + /** Metadata */ + metadata?: Record; + /** Tags */ + tags?: (string)[]; + /** + * Referrer Url + * @default + */ + referrer_url?: string; + subscriber_type?: components["schemas"]["SubscriberType"]; + }; + /** NewsletterOutput */ + NewsletterOutput: { + /** + * Id + * Format: uuid + */ + id: string; + /** Username */ + username: string; + /** Name */ + name: string; + /** Description */ + description: string; + /** + * Creation Date + * Format: date-time + */ + creation_date: string; + /** + * Api Key + * Format: uuid + */ + api_key: string; + }; + /** Page[NewsletterOutput] */ + Page_NewsletterOutput_: { + /** Results */ + results: (components["schemas"]["NewsletterOutput"])[]; + /** Count */ + count: number; + }; + /** + * CreateNewsletterErrorCode + * @description An enumeration. + * @enum {string} + */ + CreateNewsletterErrorCode: "username_already_exists"; + /** ErrorMessage[CreateNewsletterErrorCode] */ + ErrorMessage_CreateNewsletterErrorCode_: { + code: components["schemas"]["CreateNewsletterErrorCode"]; + /** Detail */ + detail: string; + /** Metadata */ + metadata?: { + [key: string]: string | undefined; + }; + }; + /** NewsletterInput */ + NewsletterInput: { + /** Username */ + username: string; + /** Name */ + name: string; + /** Description */ + description: string; + }; + /** NewsletterUpdateInput */ + NewsletterUpdateInput: { + /** Username */ + username?: string; + /** Name */ + name?: string; + /** Description */ + description?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type external = Record; + +export interface operations { + + api_views_exports_list_exports: { + /** List Exports */ + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["Page_ExportOutput_"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_exports_create_export: { + /** Create Export */ + requestBody?: { + content: { + "application/json": components["schemas"]["ExportInput"]; + }; + }; + responses: { + /** @description Created */ + 201: { + content: { + "application/json": components["schemas"]["ExportOutput"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_exports_retrieve_export: { + /** Retrieve Export */ + parameters: { + path: { + export_id: string; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["ExportOutput"]; + }; + }; + }; + }; + api_views_subscriber_tags_list_tags: { + /** List Tags */ + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["Page_TagOutput_"]; + }; + }; + }; + }; + api_views_subscriber_tags_create_tag: { + /** Create Tag */ + requestBody: { + content: { + "application/json": components["schemas"]["TagInput"]; + }; + }; + responses: { + /** @description Created */ + 201: { + content: { + "application/json": components["schemas"]["TagOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + /** @description Forbidden */ + 403: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_subscriber_tags_retrieve_tag: { + /** Retrieve Tag */ + parameters: { + path: { + tag_id: string; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["TagOutput"]; + }; + }; + }; + }; + api_views_subscriber_tags_delete_tag: { + /** Delete Tag */ + parameters: { + path: { + tag_id: string; + }; + }; + responses: { + /** @description No Content */ + 204: never; + }; + }; + api_views_subscriber_tags_update_tag: { + /** Update Tag */ + parameters: { + path: { + tag_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TagUpdateInput"]; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["TagOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage_UpdateTagErrorCode_"]; + }; + }; + }; + }; + api_views_ping_ping: { + /** Ping */ + responses: { + /** @description OK */ + 200: never; + }; + }; + api_views_images_create_image: { + /** Create Image */ + requestBody: { + content: { + "multipart/form-data": { + /** + * Image + * Format: binary + */ + image: string; + }; + }; + }; + responses: { + /** @description Created */ + 201: { + content: { + "application/json": components["schemas"]["ImageOutput"]; + }; + }; + }; + }; + api_views_images_delete_image: { + /** Delete Image */ + parameters: { + path: { + image_id: string; + }; + }; + responses: { + /** @description No Content */ + 204: never; + }; + }; + api_views_emails_list_emails: { + /** List Emails */ + parameters?: { + query?: { + status?: (components["schemas"]["EmailStatus"])[]; + included_tags?: (string)[]; + excluded_tags?: (string)[]; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["Page_EmailOutput_"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_emails_create_email: { + /** Create Email */ + requestBody: { + content: { + "application/json": components["schemas"]["EmailInput"]; + }; + }; + responses: { + /** @description Created */ + 201: { + content: { + "application/json": components["schemas"]["EmailOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage_EmailCreationErrorCode_"]; + }; + }; + }; + }; + api_views_emails_retrieve_email: { + /** Retrieve Email */ + parameters: { + path: { + pk: string; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["EmailOutput"]; + }; + }; + }; + }; + api_views_emails_retrieve_email_analytics: { + /** Retrieve Email Analytics */ + parameters: { + path: { + pk: string; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["Analytics"]; + }; + }; + }; + }; + api_views_subscribers_list_subscribers: { + /** List Subscribers */ + parameters?: { + /** @description An enumeration. */ + query?: { + type?: "regular" | "premium" | "churning" | "past_due" | "gifted" | "unpaid" | "unactivated" | "unsubscribed" | "spammy" | "removed" | "trialed" | "disabled" | "paused"; + email?: string; + tag?: string; + "-tag"?: string; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["Page_SubscriberOutput_"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage_ListSubscribersErrorCode_"]; + }; + }; + }; + }; + api_views_subscribers_create_subscriber: { + /** Create Subscriber */ + requestBody: { + content: { + "application/json": components["schemas"]["SubscriberInput"]; + }; + }; + responses: { + /** @description Created */ + 201: { + content: { + "application/json": components["schemas"]["SubscriberOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_subscribers_retrieve_subscriber: { + /** Retrieve Subscriber */ + parameters: { + path: { + pk: string; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["SubscriberOutput"]; + }; + }; + }; + }; + api_views_subscribers_delete_subscriber: { + /** Delete Subscriber */ + parameters: { + path: { + pk: string; + }; + }; + responses: { + /** @description No Content */ + 204: never; + }; + }; + api_views_subscribers_update_subscriber: { + /** Update Subscriber */ + parameters: { + path: { + pk: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SubscriberUpdateInput"]; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["SubscriberOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage_UpdateSubscriberErrorCode_"]; + }; + }; + }; + }; + api_views_subscribers_send_reminder: { + /** Send Reminder */ + parameters: { + path: { + pk: string; + }; + }; + responses: { + /** @description OK */ + 200: never; + }; + }; + api_views_subscribers_send_email_to: { + /** Send Email To */ + parameters: { + path: { + pk: string; + email_pk: string; + }; + }; + responses: { + /** @description OK */ + 200: never; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_newsletters_list_newsletters: { + /** List Newsletters */ + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["Page_NewsletterOutput_"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; + api_views_newsletters_create_newsletter: { + /** Create Newsletter */ + requestBody: { + content: { + "application/json": components["schemas"]["NewsletterInput"]; + }; + }; + responses: { + /** @description Created */ + 201: { + content: { + "application/json": components["schemas"]["NewsletterOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage_CreateNewsletterErrorCode_"]; + }; + }; + }; + }; + api_views_newsletters_delete_newsletter: { + /** Delete Newsletter */ + parameters: { + path: { + pk: string; + }; + }; + responses: { + /** @description No Content */ + 204: never; + }; + }; + api_views_newsletters_update_newsletter: { + /** Update Newsletter */ + parameters: { + path: { + pk: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NewsletterUpdateInput"]; + }; + }; + responses: { + /** @description OK */ + 200: { + content: { + "application/json": components["schemas"]["NewsletterOutput"]; + }; + }; + /** @description Bad Request */ + 400: { + content: { + "application/json": components["schemas"]["ErrorMessage"]; + }; + }; + }; + }; +} \ No newline at end of file diff --git a/pages/api-reference/exports/index.mdx b/pages/api-reference/exports/index.mdx index cc82f86..73e17e1 100644 --- a/pages/api-reference/exports/index.mdx +++ b/pages/api-reference/exports/index.mdx @@ -5,20 +5,28 @@ description: An API reference for the 'export' object in Buttondown import Layout from "../../../components/Layout"; import ResponsesTable from "../../../components/api/ResponsesTable"; +import PathTable from "../../../components/api/PathTable"; import ObjectDescription from "../../../components/api/ObjectDescription"; import ParametersTable from "../../../components/api/ParametersTable"; import Endpoint from "../../../components/api/Endpoint"; import DATA from "./metadata.json"; -import { PAGE_PARAMETER } from '../../../lib/parameters'; +import { PAGE_PARAMETER } from "../../../lib/parameters"; +import OpenAPI from "../../../lib/openapi.json"; +import OpenAPIFixtures from "../../../lib/fixtures.json"; + export const meta = { title: DATA.title, }; -export const serializedExport = JSON.stringify(DATA.object, null, 4); +export const key = "ExportOutput"; +export const schema = OpenAPI.components.schemas[key]; +export const fixture = JSON.stringify(OpenAPIFixtures[key], null, 4); export default ({ children }) => {children}; +export const foo = console.log(OpenAPIFixtures[key]) && 3; + # Exports Some software applications may want programmatic access to their newsletter exports. This assists with a few niche @@ -30,67 +38,23 @@ on email events. In general, you probably won't _need_ to use this endpoint unle An export looks like this: ({ + field: key, + type: schema.properties[key].type || schema.properties[key]["$ref"], + description: schema.properties[key].description, + }))} /> - - + + --- - - ---- - -"} -/> - + --- @@ -99,12 +63,4 @@ An export looks like this: method="GET" path={DATA.endpoint + "/"} /> - + diff --git a/pages/api-reference/exports/metadata.json b/pages/api-reference/exports/metadata.json index a6c4eb5..585ee8b 100644 --- a/pages/api-reference/exports/metadata.json +++ b/pages/api-reference/exports/metadata.json @@ -1,12 +1,5 @@ { "title": "Export", "noun": "export", - "endpoint": "/v1/exports", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "completion_date": "2019-08-24T15:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "url": "s3://buttondown/path-to-export", - "status": "completed" - } + "endpoint": "/v1/exports" } diff --git a/pages/api-reference/subscribers/index.mdx b/pages/api-reference/subscribers/index.mdx index ceeaf0b..ab143c8 100644 --- a/pages/api-reference/subscribers/index.mdx +++ b/pages/api-reference/subscribers/index.mdx @@ -8,6 +8,8 @@ import Table from "../../../components/Table"; import Pill from "../../../components/Pill"; import ObjectDescription from "../../../components/api/ObjectDescription"; import ParametersTable from "../../../components/api/ParametersTable"; +import PathTable from "../../../components/api/PathTable"; +import EnumTable from "../../../components/api/EnumTable"; import ResponsesTable from "../../../components/api/ResponsesTable"; import Endpoint from "../../../components/api/Endpoint"; import ClosedBetaNotice from "../../../components/api/ClosedBetaNotice"; @@ -16,9 +18,16 @@ import { H2, H1, P, H3, H4, Pre, Code } from "../../../components/Markdown"; import { CheckCircleIcon } from "@heroicons/react/outline"; import DATA from "./metadata.json"; import { PAGE_PARAMETER } from "../../../lib/parameters"; +import OpenAPI from "../../../lib/openapi.json"; +import OpenAPIFixtures from "../../../lib/fixtures.json"; +import OpenAPIEnums from "../../../lib/enums.json"; export const custom = () =>

; +export const key = "SubscriberOutput"; +export const schema = OpenAPI.components.schemas[key]; +export const fixture = JSON.stringify(OpenAPIFixtures[key], null, 4); + export default ({ children }) => {children}; export const meta = { title: "Subscribers", @@ -34,25 +43,12 @@ They're what you see on your [subscribers page](https://buttondown.email/subscri A subscriber looks like this: SubscriberType, - }, - { field: "source", type: "string" }, - { field: "tags", type: "array" }, - { field: "utm_campaign", type: "string" }, - { field: "utm_medium", type: "string" }, - { field: "utm_source", type: "string" }, - ]} + example={fixture} + fields={Object.keys(schema.properties).map((key) => ({ + field: key, + type: schema.properties[key].type || schema.properties[key]["$ref"], + description: schema.properties[key].description, + }))} /> --- @@ -99,99 +95,11 @@ A subscriber looks like this: ]} /> - + #### Subscriber types -

( - {row.name || row.type} - ), - }, - { - title: "identifier", - component: (s) => {s.type}, - }, - { title: "description" }, - ]} - content={[ - { - variant: "success", - type: "regular", - description: - "normal subscribers who have not unsubscribed or deactivated in any way", - }, - { - variant: "success", - type: "premium", - description: "subscribers with active premium subscriptions", - }, - { - variant: "info", - type: "trialed", - description: - "subscribers that are temporarily receiving a premium subscription to your newsletter", - }, - { - variant: "info", - type: "unpaid", - description: - "subscribers who have not yet purchased a subscription to your newsletter", - }, - { - variant: "info", - type: "gifted", - description: - "subscribers that have been gifted free premium subscriptions", - }, - { - variant: "warning", - type: "churning", - description: - "subscribers who have elected to not renew their subscription to your newsletter and will become unpaid subscribers at the end of their current billing period", - }, - { - variant: "warning", - type: "unactivated", - description: - "subscribers who have not yet confirmed their email or opted in", - }, - { - variant: "warning", - type: "paused", - description: - "subscribers that are on a temporary hold from their premium subscription, but are still subscribed to your newsletter", - }, - { - variant: "error", - type: "unsubscribed", - description: - "subscribers that have voluntarily unsubscribed from your newsletter", - }, - { - variant: "error", - type: "spammy", - description: - "subscribers that have been deemed spammy by Buttondown's automated systems", - }, - { - variant: "error", - type: "removed", - description: - "subscribers who have been explicitly removed by the newsletter (notably, this does not mean unsubscribers: use /v1/unsubscribers for that!)", - }, - { - variant: "error", - type: "past_due", - name: "Past due", - description: - "subscribers who technically have active paid subscriptions, but have not paid their invoices in time", - }, - ]} -/> + @@ -357,7 +265,7 @@ If Buttondown cannot create a new subscriber with the email address you've provi { Status: "200", description: "", - "Sample Response": DATA.object, + "Sample Response": {}, }, ]} /> diff --git a/pages/api-reference/subscribers/metadata.json b/pages/api-reference/subscribers/metadata.json index 4334572..03a6a41 100644 --- a/pages/api-reference/subscribers/metadata.json +++ b/pages/api-reference/subscribers/metadata.json @@ -1,22 +1,5 @@ { "title": "Subscriber", "noun": "subscriber", - "endpoint": "/v1/subscribers", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "email": "user@example.com", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "metadata": { - "foo": "bar" - }, - "notes": "", - "referrer_url": "http://jmduke.com/", - "secondary_id": 3, - "source": "api", - "subscriber_type": "regular", - "tags": [], - "utm_campaign": "", - "utm_medium": "", - "utm_source": "" - } + "endpoint": "/v1/subscribers" } From 5b7ffa3183e9580d75f6b75131478fc00d5f773a Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Tue, 6 Dec 2022 16:17:11 -0500 Subject: [PATCH 02/11] WIP --- components/api/EndpointDescription.tsx | 35 ++ components/api/ObjectDescription.tsx | 55 ++- lib/README.md | 1 + lib/enums.json | 112 ++++++ lib/fixtures.json | 22 +- lib/openapi-utils.ts | 9 + lib/openapi.json | 357 ++++++++++++++++++-- pages/api-reference/bulk-actions.mdx | 49 +++ pages/api-reference/event_types.json | 42 --- pages/api-reference/events-and-webhooks.mdx | 4 +- 10 files changed, 589 insertions(+), 97 deletions(-) create mode 100644 components/api/EndpointDescription.tsx create mode 100644 lib/README.md create mode 100644 lib/openapi-utils.ts create mode 100644 pages/api-reference/bulk-actions.mdx delete mode 100644 pages/api-reference/event_types.json diff --git a/components/api/EndpointDescription.tsx b/components/api/EndpointDescription.tsx new file mode 100644 index 0000000..dcd4535 --- /dev/null +++ b/components/api/EndpointDescription.tsx @@ -0,0 +1,35 @@ +import Endpoint from "./Endpoint"; +import ParametersTable from "./ParametersTable"; +import PathTable from "./PathTable"; +import OpenAPI from "../../lib/openapi.json"; +import { extractRefFromType } from "../../lib/openapi-utils"; + +export default function EndpointDescription({ path }: { path: string }) { + const methods = Object.keys(OpenAPI.paths[path]); + return methods.map((method) => { + const operation = OpenAPI.paths[path][method]; + const parameters = + operation.requestBody?.content["application/json"].schema.$ref; + const schema = + parameters !== undefined + ? OpenAPI.components.schemas[extractRefFromType(parameters)] + : null; + return ( +
+ + {parameters && ( + { + return { + parameter, + type: schema.properties[parameter].type, + description: schema.properties[parameter].description, + }; + })} + /> + )} + +
+ ); + }); +} diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index 5d61fe4..c636ee8 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -1,6 +1,10 @@ -import { Code } from "../Markdown"; -import Table, { Row } from "../Table"; -import OpenAPIEnum from "../../lib/enums.json"; +import { Code, H4 } from "../Markdown"; +import Table from "../Table"; +import { extractRefFromType } from "../../lib/openapi-utils"; +import EnumTable from "./EnumTable"; +import OpenAPI from "../../lib/openapi.json"; +import OpenAPIEnums from "../../lib/enums"; +import EndpointDescription from "./EndpointDescription"; function MonospacedSpan(s: string) { return {s}; @@ -15,21 +19,16 @@ type Field = { type Props = { example: string; fields: Array; + enums: Array; + endpoints: Array; }; -const extractRefFromType = (type: string) => { - // If #/components/schemas/ is present, extract the name of the schema - const match = type.match(/#\/components\/schemas\/(.*)/); - if (match) { - const ref = match[1]; - if (OpenAPIEnum[ref] !== undefined) { - return ref; - } - } - return null; -}; - -export default function ObjectDescription({ example, fields }: Props) { +export default function ObjectDescription({ + example, + fields, + enums, + endpoints, +}: Props) { return (
{example} @@ -59,6 +58,30 @@ export default function ObjectDescription({ example, fields }: Props) { ]} content={fields} /> + + {enums !== undefined && + enums.map((e) => { + return ( + + ); + })} + + {endpoints !== undefined && + endpoints.map((e) => { + return ( +
+ +
+ ); + })}
); } diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..64e5853 --- /dev/null +++ b/lib/README.md @@ -0,0 +1 @@ +# openapi diff --git a/lib/enums.json b/lib/enums.json index f68c099..3288e47 100644 --- a/lib/enums.json +++ b/lib/enums.json @@ -1,4 +1,116 @@ { + "EventType": { + "subscriber.created": { + "name": "subscriber.created", + "description": "Whenever a new subscriber is created" + }, + "subscriber.unsubscribed": { + "name": "subscriber.unsubscribed", + "description": "When a subscriber has manually unsubscribed from your newsletter" + }, + "subscriber.confirmed": { + "name": "subscriber.confirmed", + "description": "When a subscriber has confirmed that they are enrolled in your newsletter. (For newsletters without double opt-in, this event is created immediately after `subscriber.created`.)" + }, + "subscriber.trial_started": { + "name": "subscriber.trial_started", + "description": "When the trial has started for a subscriber. (For newsletters with automatic free trials, this will be created immediately after `subscriber.confirmed`.)" + }, + "subscriber.trial_ended": { + "name": "subscriber.trial_ended", + "description": "When the trial has ended for a subscriber." + }, + "subscriber.paid": { + "name": "subscriber.paid", + "description": "When a subscriber has enrolled in your newsletter's paid offering" + }, + "subscriber.churned": { + "name": "subscriber.churned", + "description": "When a subscriber has unenrolled from your newsletter's paid offering" + }, + "subscriber.updated": { + "name": "subscriber.updated", + "description": "When a subscriber's notes or metadata has changed" + }, + "email.created": { + "name": "email.created", + "description": "Whenever a new email is created and begins delivery. Note that event happens immediately before the emails themselves are sent." + }, + "email.sent": { + "name": "email.sent", + "description": "Whenever an email has finished its delivery. Note that event happens immediately after all emails have been sent, but some email events may not have finished processing." + } + }, + "BulkActionStatus": { + "not_started": { + "variant": "info", + "name": "Not started", + "description": "The bulk action has not yet started" + }, + "in_progress": { + "variant": "info", + "name": "In progress", + "description": "The bulk action is currently being processed" + }, + "processed": { + "variant": "success", + "description": "The bulk action has completed" + }, + "failed": { + "variant": "error", + "description": "The bulk action was unable to be completed. Buttondown is looking into it." + } + }, + "BulkActionType": { + "apply_tags": { + "variant": "info", + "name": "Apply tags" + }, + "ban_subscribers": { + "variant": "info", + "name": "Ban subscribers" + }, + "delete_emails": { + "variant": "info", + "name": "Delete emails" + }, + "delete_subscribers": { + "variant": "info", + "name": "Delete subscribers" + }, + "delete_tags": { + "variant": "info", + "name": "Delete tags" + }, + "reactivate_subscribers": { + "variant": "info", + "name": "Reactivate subscribers" + }, + "replay_events": { + "variant": "info", + "name": "Replay events" + }, + "resubscribe_subscribers": { + "variant": "info", + "name": "Resubscribe subscribers" + }, + "send_emails": { + "variant": "info", + "name": "Send emails" + }, + "send_reminders": { + "variant": "info", + "name": "Send reminders" + }, + "update_email_types": { + "variant": "info", + "name": "Update email types" + }, + "unsubscribe_subscribers": { + "variant": "info", + "name": "Unsubscribe subscribers" + } + }, "SubscriberType": { "regular": { "variant": "success", diff --git a/lib/fixtures.json b/lib/fixtures.json index fe58c38..59f7a81 100644 --- a/lib/fixtures.json +++ b/lib/fixtures.json @@ -6,25 +6,13 @@ "url": "s3://buttondown/path-to-export", "status": "completed" }, + "BulkActionOutput": { + "creation_date": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "status": "completed" + }, "ErrorMessage": { "code": "something_went_wrong", "detail": "Your call is very important to us." - }, - "SubscriberOutput": { - "creation_date": "2019-08-24T14:15:22Z", - "email": "user@example.com", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "metadata": { - "foo": "bar" - }, - "notes": "", - "referrer_url": "http://jmduke.com/", - "secondary_id": 3, - "source": "api", - "subscriber_type": "regular", - "tags": [], - "utm_campaign": "", - "utm_medium": "", - "utm_source": "" } } diff --git a/lib/openapi-utils.ts b/lib/openapi-utils.ts new file mode 100644 index 0000000..d2a2031 --- /dev/null +++ b/lib/openapi-utils.ts @@ -0,0 +1,9 @@ +export const extractRefFromType = (type: string) => { + // If #/components/schemas/ is present, extract the name of the schema + const match = type.match(/#\/components\/schemas\/(.*)/); + if (match) { + const ref = match[1]; + return ref; + } + return null; +}; diff --git a/lib/openapi.json b/lib/openapi.json index 25f77f5..0461aac 100644 --- a/lib/openapi.json +++ b/lib/openapi.json @@ -133,6 +133,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -443,6 +453,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "requestBody": { @@ -528,6 +548,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -562,6 +592,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -596,6 +636,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -630,6 +680,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "requestBody": { @@ -656,27 +716,9 @@ "in": "query", "name": "type", "schema": { - "title": "Type", - "description": "An enumeration.", - "enum": [ - "regular", - "premium", - "churning", - "past_due", - "gifted", - "unpaid", - "unactivated", - "unsubscribed", - "spammy", - "removed", - "trialed", - "disabled", - "paused" - ], - "type": "string" + "$ref": "#/components/schemas/SubscriberType" }, - "required": false, - "description": "An enumeration." + "required": false }, { "in": "query", @@ -728,6 +770,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -787,6 +839,16 @@ "responses": { "204": { "description": "No Content" + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -829,6 +891,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "requestBody": { @@ -866,6 +938,16 @@ "responses": { "200": { "description": "OK" + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -912,6 +994,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -946,6 +1038,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -978,6 +1080,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "requestBody": { @@ -1032,6 +1144,16 @@ } } } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "requestBody": { @@ -1067,6 +1189,104 @@ "responses": { "204": { "description": "No Content" + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/bulk_actions": { + "post": { + "operationId": "api_views_bulk_actions_create_bulk_action", + "summary": "Create Bulk Action", + "parameters": [], + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkActionOutput" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkActionInput" + } + } + }, + "required": true + }, + "security": [ + { + "GlobalAuth": [] + } + ] + } + }, + "/bulk_actions/{pk}": { + "get": { + "operationId": "api_views_bulk_actions_retrieve_bulk_action", + "summary": "Retrieve Bulk Action", + "parameters": [ + { + "in": "path", + "name": "pk", + "schema": { + "title": "Pk", + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkActionOutput" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } } }, "security": [ @@ -1137,6 +1357,7 @@ }, "metadata": { "title": "Metadata", + "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -1300,6 +1521,7 @@ }, "metadata": { "title": "Metadata", + "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -1480,6 +1702,7 @@ }, "metadata": { "title": "Metadata", + "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -1832,6 +2055,7 @@ }, "metadata": { "title": "Metadata", + "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -1866,6 +2090,7 @@ }, "metadata": { "title": "Metadata", + "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -1993,6 +2218,7 @@ }, "metadata": { "title": "Metadata", + "default": {}, "type": "object", "additionalProperties": { "type": "string" @@ -2044,6 +2270,97 @@ "type": "string" } } + }, + "BulkActionStatus": { + "title": "Status", + "description": "An enumeration.", + "enum": [ + "not_started", + "in_progress", + "processed", + "failed" + ], + "type": "string" + }, + "BulkActionType": { + "title": "Type", + "description": "An enumeration.", + "enum": [ + "apply_tags", + "ban_subscribers", + "delete_emails", + "delete_subscribers", + "delete_tags", + "reactivate_subscribers", + "replay_events", + "resubscribe_subscribers", + "send_emails", + "send_reminders", + "update_email_types", + "unsubscribe_subscribers" + ], + "type": "string" + }, + "BulkActionOutput": { + "title": "BulkActionOutput", + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "creation_date": { + "title": "Creation Date", + "type": "string", + "format": "date-time" + }, + "status": { + "$ref": "#/components/schemas/BulkActionStatus" + }, + "type": { + "$ref": "#/components/schemas/BulkActionType" + }, + "completion_date": { + "title": "Completion Date", + "type": "string", + "format": "date-time" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "id", + "creation_date", + "status", + "type", + "metadata" + ] + }, + "BulkActionInput": { + "title": "BulkActionInput", + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/BulkActionType" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "type", + "metadata" + ] } }, "securitySchemes": { diff --git a/pages/api-reference/bulk-actions.mdx b/pages/api-reference/bulk-actions.mdx new file mode 100644 index 0000000..a8e6e0b --- /dev/null +++ b/pages/api-reference/bulk-actions.mdx @@ -0,0 +1,49 @@ +--- +title: Bulk actions +description: An API reference for the 'bulk action' object in Buttondown +--- + +import Layout from "../../components/Layout"; +import ResponsesTable from "../../components/api/ResponsesTable"; +import PathTable from "../../components/api/PathTable"; +import ObjectDescription from "../../components/api/ObjectDescription"; +import ParametersTable from "../../components/api/ParametersTable"; +import Endpoint from "../../components/api/Endpoint"; +import EndpointDescription from "../../components/api/EndpointDescription"; +import EnumTable from "../../components/api/EnumTable"; +import OpenAPIEnums from "../../lib/enums.json"; + +import { PAGE_PARAMETER } from "../../lib/parameters"; +import OpenAPI from "../../lib/openapi.json"; +import OpenAPIFixtures from "../../lib/fixtures.json"; + +export const meta = { + title: "Bulk actions", +}; + +export const key = "BulkActionOutput"; +export const schema = OpenAPI.components.schemas[key]; +export const fixture = JSON.stringify(OpenAPIFixtures[key], null, 4); + +export default ({ children }) => {children}; + +# Bulk actions + +Some software applications may want programmatic access to their newsletter exports. This assists with a few niche +use cases, such as regular backups or data ingestion (into a data warehouse), or post-publishing processes that hinge +on email events. In general, you probably won't _need_ to use this endpoint unless you _absolutely_ need to use this endpoint. + +## The bulk action object + +A bulk action looks like this: + + ({ + field: key, + type: schema.properties[key].type || schema.properties[key]["$ref"], + description: schema.properties[key].description, + }))} + enums={["BulkActionType", "BulkActionStatus"]} + endpoints={["/bulk_actions", "/bulk_actions/{pk}"]} +/> diff --git a/pages/api-reference/event_types.json b/pages/api-reference/event_types.json deleted file mode 100644 index 08a000f..0000000 --- a/pages/api-reference/event_types.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "name": "subscriber.created", - "description": "Whenever a new subscriber is created" - }, - { - "name": "subscriber.unsubscribed", - "description": "When a subscriber has manually unsubscribed from your newsletter" - }, - { - "name": "subscriber.confirmed", - "description": "When a subscriber has confirmed that they are enrolled in your newsletter. (For newsletters without double opt-in, this event is created immediately after `subscriber.created`.)" - }, - { - "name": "subscriber.trial_started", - "description": "When the trial has started for a subscriber. (For newsletters with automatic free trials, this will be created immediately after `subscriber.confirmed`.)" - }, - { - "name": "subscriber.trial_ended", - "description": "When the trial has ended for a subscriber." - }, - { - "name": "subscriber.paid", - "description": "When a subscriber has enrolled in your newsletter's paid offering" - }, - { - "name": "subscriber.churned", - "description": "When a subscriber has unenrolled from your newsletter's paid offering" - }, - { - "name": "subscriber.updated", - "description": "When a subscriber's notes or metadata has changed" - }, - { - "name": "email.created", - "description": "Whenever a new email is created and begins delivery. Note that event happens immediately before the emails themselves are sent." - }, - { - "name": "email.sent", - "description": "Whenever an email has finished its delivery. Note that event happens immediately after all emails have been sent, but some email events may not have finished processing." - } -] diff --git a/pages/api-reference/events-and-webhooks.mdx b/pages/api-reference/events-and-webhooks.mdx index f47d991..2a58065 100644 --- a/pages/api-reference/events-and-webhooks.mdx +++ b/pages/api-reference/events-and-webhooks.mdx @@ -5,7 +5,7 @@ description: How to get a full lens of your Buttondown newsletter through struct import Layout from "../../components/Layout"; import Table from "../../components/Table"; -import EVENT_TYPES from "./event_types.json"; +import OpenAPIEnums from "../../lib/enums.json"; export const meta = { title: "Events & webhooks", @@ -33,5 +33,5 @@ The payload of most events are fairly simple, and look something like this:
From f27b94a8ffd355a88b5ea6dd1a6e4fb3ed63b2ea Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Wed, 7 Dec 2022 12:09:22 -0500 Subject: [PATCH 03/11] More WIP --- components/api/Endpoint.tsx | 2 +- components/api/ObjectDescription.tsx | 33 +- components/api/ResponsesTable.tsx | 4 +- lib/enums.json | 6 +- lib/fixtures.json | 67 +- lib/openapi.json | 83 +- pages/api-reference/bulk-actions.mdx | 19 +- public/schema.yaml | 1075 +++++++++++++------------- public/search-results.json | 52 +- 9 files changed, 735 insertions(+), 606 deletions(-) diff --git a/components/api/Endpoint.tsx b/components/api/Endpoint.tsx index 04a6ca8..63b40d2 100644 --- a/components/api/Endpoint.tsx +++ b/components/api/Endpoint.tsx @@ -19,7 +19,7 @@ export default function Endpoint({ title, method, path, beta }: Props) { )}
-        {method} → https://api.buttondown.email{path}
+        {method} → https://api.buttondown.email/v1{path}
       
); diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index c636ee8..f21f4e4 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -1,9 +1,10 @@ -import { Code, H4 } from "../Markdown"; +import { Code, H4, P } from "../Markdown"; import Table from "../Table"; import { extractRefFromType } from "../../lib/openapi-utils"; import EnumTable from "./EnumTable"; import OpenAPI from "../../lib/openapi.json"; import OpenAPIEnums from "../../lib/enums"; +import OpenAPIFixtures from "../../lib/fixtures.json"; import EndpointDescription from "./EndpointDescription"; function MonospacedSpan(s: string) { @@ -17,21 +18,35 @@ type Field = { }; type Props = { - example: string; + name: string; fields: Array; enums: Array; endpoints: Array; }; -export default function ObjectDescription({ - example, - fields, - enums, - endpoints, -}: Props) { +export default function ObjectDescription({ name, enums, endpoints }: Props) { + const schema = OpenAPI.components.schemas[name]; + const fields = Object.keys(schema.properties).map((key) => ({ + field: key, + type: schema.properties[key].type || schema.properties[key]["$ref"], + description: schema.properties[key].description, + })); + const fixtures = OpenAPIFixtures[name]; + return (
- {example} +

{schema.description}

+ {fixtures.length > 0 && + fixtures.map((fixture) => { + return ( + <> +

{fixture.description}

+ + {JSON.stringify(fixture.object, null, 4)} + + + ); + })}
diff --git a/components/api/ResponsesTable.tsx b/components/api/ResponsesTable.tsx index 741df01..a1055a2 100644 --- a/components/api/ResponsesTable.tsx +++ b/components/api/ResponsesTable.tsx @@ -25,13 +25,13 @@ const fixtureForRef = (ref: string) => { // We want to extract 'FOO'. if (ref.startsWith("Page_")) { const pageName = ref.split("_")[1]; - const pageRef = OpenAPIFixtures[pageName]; + const pageRef = OpenAPIFixtures[pageName][0].object; return { results: [pageRef], count: 1, }; } - return OpenAPIFixtures[ref] || ref; + return OpenAPIFixtures[ref][0].object || ref; }; function SampleResponse(text: any) { diff --git a/lib/enums.json b/lib/enums.json index 3288e47..b14024e 100644 --- a/lib/enums.json +++ b/lib/enums.json @@ -64,7 +64,8 @@ "BulkActionType": { "apply_tags": { "variant": "info", - "name": "Apply tags" + "name": "Apply tags", + "description": "This action requires two additional parameters within `metadata`: `tag_id` (the ID of the tag which you'd like to either add or remove to the list of subscribers) and `action` (which can be either `add` or `remove`)." }, "ban_subscribers": { "variant": "info", @@ -96,7 +97,8 @@ }, "send_emails": { "variant": "info", - "name": "Send emails" + "name": "Send emails", + "description": "This action requires one additional parameter within `metadata`: `email_id` (the ID of the email which you'd like to send)." }, "send_reminders": { "variant": "info", diff --git a/lib/fixtures.json b/lib/fixtures.json index 59f7a81..f308bef 100644 --- a/lib/fixtures.json +++ b/lib/fixtures.json @@ -1,18 +1,53 @@ { - "ExportOutput": { - "creation_date": "2019-08-24T14:15:22Z", - "completion_date": "2019-08-24T15:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "url": "s3://buttondown/path-to-export", - "status": "completed" - }, - "BulkActionOutput": { - "creation_date": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "status": "completed" - }, - "ErrorMessage": { - "code": "something_went_wrong", - "detail": "Your call is very important to us." - } + "Export": [ + { + "description": "Basic export", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T15:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "url": "s3://buttondown/path-to-export", + "status": "completed" + } + } + ], + "BulkAction": [ + { + "description": "Basic bulk action", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T14:17:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "status": "completed", + "type": "delete_subscribers", + "metadata": { + "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + } + } + }, + { + "description": "Bulk action to apply tags (which has a slightly different payload)", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T14:17:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "status": "completed", + "type": "apply_tags", + "metadata": { + "tag": "4931-4f5c-9b5b-2c9f6c9f6c9f", + "action": "add", + "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + } + } + } + ], + "ErrorMessage": [ + { + "description": "Basic error", + "object": { + "code": "something_went_wrong", + "detail": "Your call is very important to us." + } + } + ] } diff --git a/lib/openapi.json b/lib/openapi.json index 0461aac..d0dbc06 100644 --- a/lib/openapi.json +++ b/lib/openapi.json @@ -1219,7 +1219,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkActionOutput" + "$ref": "#/components/schemas/BulkAction" } } } @@ -1273,7 +1273,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BulkActionOutput" + "$ref": "#/components/schemas/BulkAction" } } } @@ -1408,6 +1408,14 @@ "$ref": "#/components/schemas/ExportOutput" } }, + "next": { + "title": "Next", + "type": "string" + }, + "previous": { + "title": "Previous", + "type": "string" + }, "count": { "title": "Count", "type": "integer" @@ -1490,6 +1498,14 @@ "$ref": "#/components/schemas/TagOutput" } }, + "next": { + "title": "Next", + "type": "string" + }, + "previous": { + "title": "Previous", + "type": "string" + }, "count": { "title": "Count", "type": "integer" @@ -1804,6 +1820,14 @@ "$ref": "#/components/schemas/EmailOutput" } }, + "next": { + "title": "Next", + "type": "string" + }, + "previous": { + "title": "Previous", + "type": "string" + }, "count": { "title": "Count", "type": "integer" @@ -2024,6 +2048,14 @@ "$ref": "#/components/schemas/SubscriberOutput" } }, + "next": { + "title": "Next", + "type": "string" + }, + "previous": { + "title": "Previous", + "type": "string" + }, "count": { "title": "Count", "type": "integer" @@ -2187,6 +2219,14 @@ "$ref": "#/components/schemas/NewsletterOutput" } }, + "next": { + "title": "Next", + "type": "string" + }, + "previous": { + "title": "Previous", + "type": "string" + }, "count": { "title": "Count", "type": "integer" @@ -2273,7 +2313,7 @@ }, "BulkActionStatus": { "title": "Status", - "description": "An enumeration.", + "description": "\n Represents the status of a bulk action.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", "enum": [ "not_started", "in_progress", @@ -2284,7 +2324,7 @@ }, "BulkActionType": { "title": "Type", - "description": "An enumeration.", + "description": "\n Represents the action being performed on a bulk of objects.\n\n (Not to be coy, but these names should be self-explanatory.)\n ", "enum": [ "apply_tags", "ban_subscribers", @@ -2301,8 +2341,9 @@ ], "type": "string" }, - "BulkActionOutput": { - "title": "BulkActionOutput", + "BulkAction": { + "title": "BulkAction", + "description": "A bulk action represents, well, a bulk action. It is used to perform\nactions on a large number of objects at once. For example, you can\nuse it to delete a large number of emails, or to unsubscribe a large\nnumber of subscribers. The actions within a bulk action are processed\nserially by Buttondown; this should be considered an ergonomic way to\nbatch API calls across the network rather than a net-new piece of functionality\nin of itself.", "type": "object", "properties": { "id": { @@ -2330,7 +2371,20 @@ "title": "Metadata", "type": "object", "additionalProperties": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object" + } + ] } } }, @@ -2353,7 +2407,20 @@ "title": "Metadata", "type": "object", "additionalProperties": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object" + } + ] } } }, diff --git a/pages/api-reference/bulk-actions.mdx b/pages/api-reference/bulk-actions.mdx index a8e6e0b..78dd4ca 100644 --- a/pages/api-reference/bulk-actions.mdx +++ b/pages/api-reference/bulk-actions.mdx @@ -21,29 +21,12 @@ export const meta = { title: "Bulk actions", }; -export const key = "BulkActionOutput"; -export const schema = OpenAPI.components.schemas[key]; -export const fixture = JSON.stringify(OpenAPIFixtures[key], null, 4); - export default ({ children }) => {children}; # Bulk actions -Some software applications may want programmatic access to their newsletter exports. This assists with a few niche -use cases, such as regular backups or data ingestion (into a data warehouse), or post-publishing processes that hinge -on email events. In general, you probably won't _need_ to use this endpoint unless you _absolutely_ need to use this endpoint. - -## The bulk action object - -A bulk action looks like this: - ({ - field: key, - type: schema.properties[key].type || schema.properties[key]["$ref"], - description: schema.properties[key].description, - }))} + name={"BulkAction"} enums={["BulkActionType", "BulkActionStatus"]} endpoints={["/bulk_actions", "/bulk_actions/{pk}"]} /> diff --git a/public/schema.yaml b/public/schema.yaml index 0e1b741..0196199 100644 --- a/public/schema.yaml +++ b/public/schema.yaml @@ -19,8 +19,8 @@ components: subject: type: string required: - - subject - - body + - subject + - body type: object Email: properties: @@ -28,10 +28,10 @@ components: type: string email_type: enum: - - public - - private - - premium - - promoted + - public + - private + - premium + - promoted type: string excluded_tags: nullable: true @@ -39,7 +39,8 @@ components: external_url: format: uri maxLength: 200 - pattern: "^(?:[a-z0-9\\.\\-\\+]*)://(?:[^\\s:@/]+(?::[^\\s:@/]*)?@)?(?:(?:25[0-5]|2[0-4]\\\ + pattern: + "^(?:[a-z0-9\\.\\-\\+]*)://(?:[^\\s:@/]+(?::[^\\s:@/]*)?@)?(?:(?:25[0-5]|2[0-4]\\\ d|[0-1]?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}|\\[[0-9a-f:\\\ .]+\\]|([a-z\xA1-\uFFFF0-9](?:[a-z\xA1-\uFFFF0-9-]{0,61}[a-z\xA1-\uFFFF\ 0-9])?(?:\\.(?!-)[a-z\xA1-\uFFFF0-9-]{1,63}(? Date: Wed, 7 Dec 2022 13:14:10 -0500 Subject: [PATCH 04/11] Closer --- components/Pill.tsx | 4 +- components/api/EndpointDescription.tsx | 88 ++++++++++++++++++-------- components/api/EnumTable.tsx | 14 +++- components/api/ObjectDescription.tsx | 38 +++++++---- lib/openapi-utils.ts | 8 ++- pages/api-reference/exports/index.mdx | 1 + public/search-results.json | 6 ++ 7 files changed, 114 insertions(+), 45 deletions(-) diff --git a/components/Pill.tsx b/components/Pill.tsx index e9d205f..2b92da2 100644 --- a/components/Pill.tsx +++ b/components/Pill.tsx @@ -2,9 +2,11 @@ import { ReactNode } from "react"; import classNames from "../lib/classNames"; // This should match the Pill in `buttondown-app`. + +export type Variant = "success" | "info" | "error" | "warning"; type Props = { children: ReactNode; - variant: "success" | "info" | "error" | "warning"; + variant: Variant; }; const Pill = (props: Props) => ( diff --git a/components/api/EndpointDescription.tsx b/components/api/EndpointDescription.tsx index dcd4535..fa0f02c 100644 --- a/components/api/EndpointDescription.tsx +++ b/components/api/EndpointDescription.tsx @@ -4,32 +4,64 @@ import PathTable from "./PathTable"; import OpenAPI from "../../lib/openapi.json"; import { extractRefFromType } from "../../lib/openapi-utils"; -export default function EndpointDescription({ path }: { path: string }) { - const methods = Object.keys(OpenAPI.paths[path]); - return methods.map((method) => { - const operation = OpenAPI.paths[path][method]; - const parameters = - operation.requestBody?.content["application/json"].schema.$ref; - const schema = - parameters !== undefined - ? OpenAPI.components.schemas[extractRefFromType(parameters)] - : null; - return ( -
- - {parameters && ( - { - return { - parameter, - type: schema.properties[parameter].type, - description: schema.properties[parameter].description, - }; - })} - /> - )} - -
- ); - }); +type Path = keyof typeof OpenAPI.paths; +type Operation = { + summary: string; + parameters: any[]; + responses: any; + requestBody?: { + content: { + "application/json": { + schema: { + $ref: string; + }; + }; + }; + }; +}; + +export default function EndpointDescription({ path }: { path: Path }) { + const informationForPath = OpenAPI.paths[path]; + const methods = Object.keys( + informationForPath + ) as (keyof typeof informationForPath)[]; + + return ( + <> + {methods.map((method) => { + const operation = informationForPath[method] as Operation; + const parameters = + operation.requestBody?.content["application/json"].schema.$ref; + + const ref = + parameters !== undefined ? extractRefFromType(parameters) : null; + const schema = ref !== null ? OpenAPI.components.schemas[ref] : null; + return ( +
+ + {parameters && schema && "properties" in schema && ( + { + const qualifiedParameter = (schema.properties as any)[ + parameter + ] as { + type: string; + description: string; + }; + return { + parameter, + type: qualifiedParameter.type, + description: qualifiedParameter.description, + }; + } + )} + /> + )} + +
+ ); + })} + + ); } diff --git a/components/api/EnumTable.tsx b/components/api/EnumTable.tsx index 0279721..6112efa 100644 --- a/components/api/EnumTable.tsx +++ b/components/api/EnumTable.tsx @@ -1,5 +1,11 @@ import Table from "../Table"; -import Pill from "../Pill"; +import Pill, { Variant } from "../Pill"; + +type Row = { + type: string; + name?: string; + variant: Variant; +}; export default function EnumTable({ e }: any) { return ( @@ -7,13 +13,15 @@ export default function EnumTable({ e }: any) { columns={[ { title: "type", - component: (row) => ( + component: (row: Row) => ( {row.name || row.type} ), }, { title: "identifier", - component: (s) => {s.type}, + component: (row: Row) => ( + {row.type} + ), }, { title: "description" }, ]} diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index f21f4e4..eb6bb87 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -3,11 +3,11 @@ import Table from "../Table"; import { extractRefFromType } from "../../lib/openapi-utils"; import EnumTable from "./EnumTable"; import OpenAPI from "../../lib/openapi.json"; -import OpenAPIEnums from "../../lib/enums"; +import OpenAPIEnums from "../../lib/enums.json"; import OpenAPIFixtures from "../../lib/fixtures.json"; import EndpointDescription from "./EndpointDescription"; -function MonospacedSpan(s: string) { +function MonospacedSpan(s: string | JSX.Element) { return {s}; } @@ -17,20 +17,36 @@ type Field = { description: string; }; +type KeysOfType = keyof { + [P in keyof T as V extends keyof T[P] ? P : never]: any; +}; + +type SchemaElementWithProperties = KeysOfType< + typeof OpenAPI.components.schemas, + "properties" +> & + KeysOfType; + type Props = { - name: string; + name: SchemaElementWithProperties & keyof typeof OpenAPIFixtures; fields: Array; - enums: Array; - endpoints: Array; + enums: Array< + keyof typeof OpenAPIEnums & keyof typeof OpenAPI.components.schemas + >; + endpoints: Array; }; export default function ObjectDescription({ name, enums, endpoints }: Props) { const schema = OpenAPI.components.schemas[name]; - const fields = Object.keys(schema.properties).map((key) => ({ - field: key, - type: schema.properties[key].type || schema.properties[key]["$ref"], - description: schema.properties[key].description, - })); + const fields = Object.entries(schema.properties).map(([key, property]) => { + return { + field: key, + // @ts-ignore + type: property.type || property["$ref"], + // @ts-ignore + description: property.description, + }; + }); const fixtures = OpenAPIFixtures[name]; return ( @@ -78,7 +94,7 @@ export default function ObjectDescription({ name, enums, endpoints }: Props) { enums.map((e) => { return (
- +

{OpenAPI.components.schemas[e].title} ({MonospacedSpan(e)})

diff --git a/lib/openapi-utils.ts b/lib/openapi-utils.ts index d2a2031..5b06852 100644 --- a/lib/openapi-utils.ts +++ b/lib/openapi-utils.ts @@ -1,9 +1,13 @@ -export const extractRefFromType = (type: string) => { +import OpenAPI from "./openapi.json"; + +export const extractRefFromType = ( + type: string +): keyof typeof OpenAPI.components.schemas | null => { // If #/components/schemas/ is present, extract the name of the schema const match = type.match(/#\/components\/schemas\/(.*)/); if (match) { const ref = match[1]; - return ref; + return ref as keyof typeof OpenAPI.components.schemas; } return null; }; diff --git a/pages/api-reference/exports/index.mdx b/pages/api-reference/exports/index.mdx index 73e17e1..5ea84d6 100644 --- a/pages/api-reference/exports/index.mdx +++ b/pages/api-reference/exports/index.mdx @@ -38,6 +38,7 @@ on email events. In general, you probably won't _need_ to use this endpoint unle An export looks like this: ({ field: key, diff --git a/public/search-results.json b/public/search-results.json index f25db1e..de06026 100644 --- a/public/search-results.json +++ b/public/search-results.json @@ -65,6 +65,12 @@ "title": "Authentication", "description": "How to authenticate API requests to Buttondown's servers" }, + { + "path": "/api-reference/bulk-actions", + "text": "Bulk actions ", + "title": "Bulk actions", + "description": "An API reference for the 'bulk action' object in Buttondown" + }, { "path": "/api-reference/changelog", "text": "Changelog The changelog provides a list of dated updates, each of which contains a number of potentially backwards-incompatible changes. There is no explicit versioning in the API at this time; all changes will be either compatible or breaking. (If I attempt any brittle changes to the API that may break current implementations, I'll be sure to add version gates.) 2022-11-24 Added , which returns aggregated information about opens, clicks, and other events for a given email that you've sent. 2022-11-19 Removed the , , and endpoints. The behavior of all three endpoints have been subsumed into and : you can create (or list) drafts by specifying you can create (or list) scheduled emails by specifying you can list unsubscribers by specifying 2022-03-08 Added support for filtering emails in on and . 2021-11-26 Took the ability to send specific emails to subscribers out of beta; added , which allows you to send\nreminders to your subscribers to confirm their email address. 2021-08-27 Added support for Exports via the endpoint. 2021-01-02 Added support to set and retrieve metadata on Emails. 2020-12-23 Added deletion and update abilities to the Scheduled emails endpoint, giving you much more programmability than the hitherto append-only state of the world. 2020-12-09 Added a deletion endpoint to the Images endpoint, allowing you to delete unused images. ", From c77fbf2a636498cd6b2a55f5ae15b878e280c4b0 Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 11:35:13 -0500 Subject: [PATCH 05/11] Store lib stuff --- lib/fixtures.json | 53 -- lib/openapi-utils.ts | 13 - lib/openapi.ts | 1057 -------------------------------- lib/{ => openapi}/enums.json | 169 +++++ lib/openapi/fixtures.json | 142 +++++ lib/{ => openapi}/openapi.json | 262 +++----- lib/openapi/types.ts | 21 + lib/openapi/utils.ts | 60 ++ 8 files changed, 473 insertions(+), 1304 deletions(-) delete mode 100644 lib/fixtures.json delete mode 100644 lib/openapi-utils.ts delete mode 100644 lib/openapi.ts rename lib/{ => openapi}/enums.json (58%) create mode 100644 lib/openapi/fixtures.json rename lib/{ => openapi}/openapi.json (92%) create mode 100644 lib/openapi/types.ts create mode 100644 lib/openapi/utils.ts diff --git a/lib/fixtures.json b/lib/fixtures.json deleted file mode 100644 index f308bef..0000000 --- a/lib/fixtures.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "Export": [ - { - "description": "Basic export", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "completion_date": "2019-08-24T15:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "url": "s3://buttondown/path-to-export", - "status": "completed" - } - } - ], - "BulkAction": [ - { - "description": "Basic bulk action", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "completion_date": "2019-08-24T14:17:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "status": "completed", - "type": "delete_subscribers", - "metadata": { - "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] - } - } - }, - { - "description": "Bulk action to apply tags (which has a slightly different payload)", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "completion_date": "2019-08-24T14:17:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "status": "completed", - "type": "apply_tags", - "metadata": { - "tag": "4931-4f5c-9b5b-2c9f6c9f6c9f", - "action": "add", - "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] - } - } - } - ], - "ErrorMessage": [ - { - "description": "Basic error", - "object": { - "code": "something_went_wrong", - "detail": "Your call is very important to us." - } - } - ] -} diff --git a/lib/openapi-utils.ts b/lib/openapi-utils.ts deleted file mode 100644 index 5b06852..0000000 --- a/lib/openapi-utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import OpenAPI from "./openapi.json"; - -export const extractRefFromType = ( - type: string -): keyof typeof OpenAPI.components.schemas | null => { - // If #/components/schemas/ is present, extract the name of the schema - const match = type.match(/#\/components\/schemas\/(.*)/); - if (match) { - const ref = match[1]; - return ref as keyof typeof OpenAPI.components.schemas; - } - return null; -}; diff --git a/lib/openapi.ts b/lib/openapi.ts deleted file mode 100644 index 3500af1..0000000 --- a/lib/openapi.ts +++ /dev/null @@ -1,1057 +0,0 @@ -/** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. - */ - - -export interface paths { - "/exports": { - /** List Exports */ - get: operations["api_views_exports_list_exports"]; - /** Create Export */ - post: operations["api_views_exports_create_export"]; - }; - "/exports/{export_id}": { - /** Retrieve Export */ - get: operations["api_views_exports_retrieve_export"]; - }; - "/tags": { - /** List Tags */ - get: operations["api_views_subscriber_tags_list_tags"]; - /** Create Tag */ - post: operations["api_views_subscriber_tags_create_tag"]; - }; - "/tags/{tag_id}": { - /** Retrieve Tag */ - get: operations["api_views_subscriber_tags_retrieve_tag"]; - /** Delete Tag */ - delete: operations["api_views_subscriber_tags_delete_tag"]; - /** Update Tag */ - patch: operations["api_views_subscriber_tags_update_tag"]; - }; - "/ping": { - /** Ping */ - get: operations["api_views_ping_ping"]; - }; - "/images": { - /** Create Image */ - post: operations["api_views_images_create_image"]; - }; - "/images/{image_id}": { - /** Delete Image */ - delete: operations["api_views_images_delete_image"]; - }; - "/emails": { - /** List Emails */ - get: operations["api_views_emails_list_emails"]; - /** Create Email */ - post: operations["api_views_emails_create_email"]; - }; - "/emails/{pk}": { - /** Retrieve Email */ - get: operations["api_views_emails_retrieve_email"]; - }; - "/emails/{pk}/analytics": { - /** Retrieve Email Analytics */ - get: operations["api_views_emails_retrieve_email_analytics"]; - }; - "/subscribers": { - /** List Subscribers */ - get: operations["api_views_subscribers_list_subscribers"]; - /** Create Subscriber */ - post: operations["api_views_subscribers_create_subscriber"]; - }; - "/subscribers/{pk}": { - /** Retrieve Subscriber */ - get: operations["api_views_subscribers_retrieve_subscriber"]; - /** Delete Subscriber */ - delete: operations["api_views_subscribers_delete_subscriber"]; - /** Update Subscriber */ - patch: operations["api_views_subscribers_update_subscriber"]; - }; - "/subscribers/{pk}/send-reminder": { - /** Send Reminder */ - post: operations["api_views_subscribers_send_reminder"]; - }; - "/subscribers/{pk}/emails/{email_pk}": { - /** Send Email To */ - post: operations["api_views_subscribers_send_email_to"]; - }; - "/newsletters": { - /** List Newsletters */ - get: operations["api_views_newsletters_list_newsletters"]; - /** Create Newsletter */ - post: operations["api_views_newsletters_create_newsletter"]; - }; - "/newsletters/{pk}": { - /** Delete Newsletter */ - delete: operations["api_views_newsletters_delete_newsletter"]; - /** Update Newsletter */ - patch: operations["api_views_newsletters_update_newsletter"]; - }; -} - -export type webhooks = Record; - -export interface components { - schemas: { - /** - * Status - * @description An enumeration. - * @enum {string} - */ - ExportStatus: "error" | "in_progress" | "not_started" | "ready"; - /** ExportOutput */ - ExportOutput: { - /** - * Id - * Format: uuid - */ - id: string; - /** - * Creation Date - * Format: date-time - */ - creation_date: string; - status: components["schemas"]["ExportStatus"]; - /** Url */ - url: string; - /** - * Completion Date - * Format: date-time - */ - completion_date?: string; - }; - /** ErrorMessage */ - ErrorMessage: { - /** Code */ - code?: Record; - /** Detail */ - detail: string; - /** Metadata */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** - * Collection - * @description An enumeration. - * @enum {string} - */ - ExportCollection: "subscribers" | "emails" | "scheduled_emails" | "drafts" | "unsubscribers" | "events" | "referrals"; - /** ExportInput */ - ExportInput: { - collections: (components["schemas"]["ExportCollection"])[]; - }; - /** Page[ExportOutput] */ - Page_ExportOutput_: { - /** Results */ - results: (components["schemas"]["ExportOutput"])[]; - /** Count */ - count: number; - }; - /** TagOutput */ - TagOutput: { - /** - * Id - * Format: uuid - */ - id: string; - /** Name */ - name: string; - /** Color */ - color: string; - /** Description */ - description?: string; - /** Secondary Id */ - secondary_id: number; - /** - * Creation Date - * Format: date-time - */ - creation_date: string; - }; - /** TagInput */ - TagInput: { - /** Name */ - name: string; - /** Color */ - color: string; - /** Description */ - description?: string; - }; - /** Page[TagOutput] */ - Page_TagOutput_: { - /** Results */ - results: (components["schemas"]["TagOutput"])[]; - /** Count */ - count: number; - }; - /** - * UpdateTagErrorCode - * @description An enumeration. - * @enum {string} - */ - UpdateTagErrorCode: "name_already_exists"; - /** ErrorMessage[UpdateTagErrorCode] */ - ErrorMessage_UpdateTagErrorCode_: { - code: components["schemas"]["UpdateTagErrorCode"]; - /** Detail */ - detail: string; - /** Metadata */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** TagUpdateInput */ - TagUpdateInput: { - /** Name */ - name?: string; - /** Color */ - color?: string; - /** Description */ - description?: string; - }; - /** ImageOutput */ - ImageOutput: { - /** - * Id - * Format: uuid - */ - id: string; - /** - * Creation Date - * Format: date-time - */ - creation_date: string; - /** Image */ - image: string; - }; - /** - * Type - * @description An enumeration. - * @enum {string} - */ - EmailType: "public" | "private" | "premium" | "free" | "archival" | "hidden"; - /** - * Status - * @description An enumeration. - * @enum {string} - */ - EmailStatus: "draft" | "about_to_send" | "scheduled" | "in_flight" | "deleted" | "errored" | "sent" | "imported"; - /** EmailOutput */ - EmailOutput: { - /** - * Id - * Format: uuid - */ - id: string; - /** Included Tags */ - included_tags?: (string)[]; - /** Excluded Tags */ - excluded_tags?: (string)[]; - /** - * Publish Date - * Format: date-time - */ - publish_date?: string; - /** Subject */ - subject: string; - /** Body */ - body: string; - /** Secondary Id */ - secondary_id?: number; - email_type: components["schemas"]["EmailType"]; - /** Slug */ - slug: string; - /** External Url */ - external_url: string; - status: components["schemas"]["EmailStatus"]; - /** - * Metadata - * @default {} - */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** - * EmailCreationErrorCode - * @description An enumeration. - * @enum {string} - */ - EmailCreationErrorCode: "subject_invalid" | "email_duplicate" | "email_invalid"; - /** ErrorMessage[EmailCreationErrorCode] */ - ErrorMessage_EmailCreationErrorCode_: { - code: components["schemas"]["EmailCreationErrorCode"]; - /** Detail */ - detail: string; - /** Metadata */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** EmailInput */ - EmailInput: { - /** - * Included Tags - * @default [] - */ - included_tags?: (string | string)[]; - /** - * Excluded Tags - * @default [] - */ - excluded_tags?: (string | string)[]; - /** - * Publish Date - * Format: date-time - */ - publish_date?: string; - /** Subject */ - subject: string; - /** - * Body - * @default - */ - body?: string; - /** @default public */ - email_type?: components["schemas"]["EmailType"]; - /** @default about_to_send */ - status?: components["schemas"]["EmailStatus"]; - /** - * Metadata - * @default {} - */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** Page[EmailOutput] */ - Page_EmailOutput_: { - /** Results */ - results: (components["schemas"]["EmailOutput"])[]; - /** Count */ - count: number; - }; - /** Analytics */ - Analytics: { - /** Recipients */ - recipients: number; - /** Deliveries */ - deliveries: number; - /** Opens */ - opens: number; - /** Clicks */ - clicks: number; - /** Temporary Failures */ - temporary_failures: number; - /** Permanent Failures */ - permanent_failures: number; - /** Unsubscriptions */ - unsubscriptions: number; - /** Complaints */ - complaints: number; - }; - /** - * Type - * @description An enumeration. - * @enum {string} - */ - SubscriberType: "regular" | "premium" | "churning" | "past_due" | "gifted" | "unpaid" | "unactivated" | "unsubscribed" | "spammy" | "removed" | "trialed" | "disabled" | "paused"; - /** - * Source - * @description An enumeration. - * @enum {string} - */ - SubscriberSource: "api" | "buttondown" | "csv" | "mailchimp" | "organic" | "nouveau" | "substack" | "tinyletter" | "typeform" | "user" | "admin" | "drip"; - /** SubscriberOutput */ - SubscriberOutput: { - /** - * Id - * Format: uuid - */ - id: string; - /** Email */ - email: string; - /** - * Notes - * @default - */ - notes?: string; - /** - * Metadata - * @default {} - */ - metadata?: Record; - /** - * Tags - * @default [] - */ - tags?: (string)[]; - /** - * Referrer Url - * @default - */ - referrer_url?: string; - /** - * Creation Date - * Format: date-time - */ - creation_date: string; - /** Secondary Id */ - secondary_id: number; - subscriber_type: components["schemas"]["SubscriberType"]; - source: components["schemas"]["SubscriberSource"]; - /** Utm Campaign */ - utm_campaign: string; - /** Utm Medium */ - utm_medium: string; - /** Utm Source */ - utm_source: string; - }; - /** SubscriberInput */ - SubscriberInput: { - /** Email */ - email: string; - /** - * Notes - * @default - */ - notes?: string; - /** - * Metadata - * @default {} - */ - metadata?: Record; - /** - * Tags - * @default [] - */ - tags?: (string)[]; - /** - * Referrer Url - * @default - */ - referrer_url?: string; - }; - /** Page[SubscriberOutput] */ - Page_SubscriberOutput_: { - /** Results */ - results: (components["schemas"]["SubscriberOutput"])[]; - /** Count */ - count: number; - }; - /** - * ListSubscribersErrorCode - * @description An enumeration. - * @enum {string} - */ - ListSubscribersErrorCode: "invalid_tag"; - /** ErrorMessage[ListSubscribersErrorCode] */ - ErrorMessage_ListSubscribersErrorCode_: { - code: components["schemas"]["ListSubscribersErrorCode"]; - /** Detail */ - detail: string; - /** Metadata */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** - * UpdateSubscriberErrorCode - * @description An enumeration. - * @enum {string} - */ - UpdateSubscriberErrorCode: "email_already_exists" | "email_invalid" | "subscriber_type_invalid"; - /** ErrorMessage[UpdateSubscriberErrorCode] */ - ErrorMessage_UpdateSubscriberErrorCode_: { - code: components["schemas"]["UpdateSubscriberErrorCode"]; - /** Detail */ - detail: string; - /** Metadata */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** SubscriberUpdateInput */ - SubscriberUpdateInput: { - /** Email */ - email?: string; - /** Notes */ - notes?: string; - /** Metadata */ - metadata?: Record; - /** Tags */ - tags?: (string)[]; - /** - * Referrer Url - * @default - */ - referrer_url?: string; - subscriber_type?: components["schemas"]["SubscriberType"]; - }; - /** NewsletterOutput */ - NewsletterOutput: { - /** - * Id - * Format: uuid - */ - id: string; - /** Username */ - username: string; - /** Name */ - name: string; - /** Description */ - description: string; - /** - * Creation Date - * Format: date-time - */ - creation_date: string; - /** - * Api Key - * Format: uuid - */ - api_key: string; - }; - /** Page[NewsletterOutput] */ - Page_NewsletterOutput_: { - /** Results */ - results: (components["schemas"]["NewsletterOutput"])[]; - /** Count */ - count: number; - }; - /** - * CreateNewsletterErrorCode - * @description An enumeration. - * @enum {string} - */ - CreateNewsletterErrorCode: "username_already_exists"; - /** ErrorMessage[CreateNewsletterErrorCode] */ - ErrorMessage_CreateNewsletterErrorCode_: { - code: components["schemas"]["CreateNewsletterErrorCode"]; - /** Detail */ - detail: string; - /** Metadata */ - metadata?: { - [key: string]: string | undefined; - }; - }; - /** NewsletterInput */ - NewsletterInput: { - /** Username */ - username: string; - /** Name */ - name: string; - /** Description */ - description: string; - }; - /** NewsletterUpdateInput */ - NewsletterUpdateInput: { - /** Username */ - username?: string; - /** Name */ - name?: string; - /** Description */ - description?: string; - }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; -} - -export type external = Record; - -export interface operations { - - api_views_exports_list_exports: { - /** List Exports */ - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["Page_ExportOutput_"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - /** @description Forbidden */ - 403: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_exports_create_export: { - /** Create Export */ - requestBody?: { - content: { - "application/json": components["schemas"]["ExportInput"]; - }; - }; - responses: { - /** @description Created */ - 201: { - content: { - "application/json": components["schemas"]["ExportOutput"]; - }; - }; - /** @description Forbidden */ - 403: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_exports_retrieve_export: { - /** Retrieve Export */ - parameters: { - path: { - export_id: string; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["ExportOutput"]; - }; - }; - }; - }; - api_views_subscriber_tags_list_tags: { - /** List Tags */ - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["Page_TagOutput_"]; - }; - }; - }; - }; - api_views_subscriber_tags_create_tag: { - /** Create Tag */ - requestBody: { - content: { - "application/json": components["schemas"]["TagInput"]; - }; - }; - responses: { - /** @description Created */ - 201: { - content: { - "application/json": components["schemas"]["TagOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - /** @description Forbidden */ - 403: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_subscriber_tags_retrieve_tag: { - /** Retrieve Tag */ - parameters: { - path: { - tag_id: string; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["TagOutput"]; - }; - }; - }; - }; - api_views_subscriber_tags_delete_tag: { - /** Delete Tag */ - parameters: { - path: { - tag_id: string; - }; - }; - responses: { - /** @description No Content */ - 204: never; - }; - }; - api_views_subscriber_tags_update_tag: { - /** Update Tag */ - parameters: { - path: { - tag_id: string; - }; - }; - requestBody: { - content: { - "application/json": components["schemas"]["TagUpdateInput"]; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["TagOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage_UpdateTagErrorCode_"]; - }; - }; - }; - }; - api_views_ping_ping: { - /** Ping */ - responses: { - /** @description OK */ - 200: never; - }; - }; - api_views_images_create_image: { - /** Create Image */ - requestBody: { - content: { - "multipart/form-data": { - /** - * Image - * Format: binary - */ - image: string; - }; - }; - }; - responses: { - /** @description Created */ - 201: { - content: { - "application/json": components["schemas"]["ImageOutput"]; - }; - }; - }; - }; - api_views_images_delete_image: { - /** Delete Image */ - parameters: { - path: { - image_id: string; - }; - }; - responses: { - /** @description No Content */ - 204: never; - }; - }; - api_views_emails_list_emails: { - /** List Emails */ - parameters?: { - query?: { - status?: (components["schemas"]["EmailStatus"])[]; - included_tags?: (string)[]; - excluded_tags?: (string)[]; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["Page_EmailOutput_"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_emails_create_email: { - /** Create Email */ - requestBody: { - content: { - "application/json": components["schemas"]["EmailInput"]; - }; - }; - responses: { - /** @description Created */ - 201: { - content: { - "application/json": components["schemas"]["EmailOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage_EmailCreationErrorCode_"]; - }; - }; - }; - }; - api_views_emails_retrieve_email: { - /** Retrieve Email */ - parameters: { - path: { - pk: string; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["EmailOutput"]; - }; - }; - }; - }; - api_views_emails_retrieve_email_analytics: { - /** Retrieve Email Analytics */ - parameters: { - path: { - pk: string; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["Analytics"]; - }; - }; - }; - }; - api_views_subscribers_list_subscribers: { - /** List Subscribers */ - parameters?: { - /** @description An enumeration. */ - query?: { - type?: "regular" | "premium" | "churning" | "past_due" | "gifted" | "unpaid" | "unactivated" | "unsubscribed" | "spammy" | "removed" | "trialed" | "disabled" | "paused"; - email?: string; - tag?: string; - "-tag"?: string; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["Page_SubscriberOutput_"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage_ListSubscribersErrorCode_"]; - }; - }; - }; - }; - api_views_subscribers_create_subscriber: { - /** Create Subscriber */ - requestBody: { - content: { - "application/json": components["schemas"]["SubscriberInput"]; - }; - }; - responses: { - /** @description Created */ - 201: { - content: { - "application/json": components["schemas"]["SubscriberOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_subscribers_retrieve_subscriber: { - /** Retrieve Subscriber */ - parameters: { - path: { - pk: string; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["SubscriberOutput"]; - }; - }; - }; - }; - api_views_subscribers_delete_subscriber: { - /** Delete Subscriber */ - parameters: { - path: { - pk: string; - }; - }; - responses: { - /** @description No Content */ - 204: never; - }; - }; - api_views_subscribers_update_subscriber: { - /** Update Subscriber */ - parameters: { - path: { - pk: string; - }; - }; - requestBody: { - content: { - "application/json": components["schemas"]["SubscriberUpdateInput"]; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["SubscriberOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage_UpdateSubscriberErrorCode_"]; - }; - }; - }; - }; - api_views_subscribers_send_reminder: { - /** Send Reminder */ - parameters: { - path: { - pk: string; - }; - }; - responses: { - /** @description OK */ - 200: never; - }; - }; - api_views_subscribers_send_email_to: { - /** Send Email To */ - parameters: { - path: { - pk: string; - email_pk: string; - }; - }; - responses: { - /** @description OK */ - 200: never; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_newsletters_list_newsletters: { - /** List Newsletters */ - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["Page_NewsletterOutput_"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; - api_views_newsletters_create_newsletter: { - /** Create Newsletter */ - requestBody: { - content: { - "application/json": components["schemas"]["NewsletterInput"]; - }; - }; - responses: { - /** @description Created */ - 201: { - content: { - "application/json": components["schemas"]["NewsletterOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage_CreateNewsletterErrorCode_"]; - }; - }; - }; - }; - api_views_newsletters_delete_newsletter: { - /** Delete Newsletter */ - parameters: { - path: { - pk: string; - }; - }; - responses: { - /** @description No Content */ - 204: never; - }; - }; - api_views_newsletters_update_newsletter: { - /** Update Newsletter */ - parameters: { - path: { - pk: string; - }; - }; - requestBody: { - content: { - "application/json": components["schemas"]["NewsletterUpdateInput"]; - }; - }; - responses: { - /** @description OK */ - 200: { - content: { - "application/json": components["schemas"]["NewsletterOutput"]; - }; - }; - /** @description Bad Request */ - 400: { - content: { - "application/json": components["schemas"]["ErrorMessage"]; - }; - }; - }; - }; -} \ No newline at end of file diff --git a/lib/enums.json b/lib/openapi/enums.json similarity index 58% rename from lib/enums.json rename to lib/openapi/enums.json index b14024e..ee337ea 100644 --- a/lib/enums.json +++ b/lib/openapi/enums.json @@ -1,4 +1,168 @@ { + "UpdateTagErrorCode": { + "name_already_exists": { + "variant": "error", + "name": "Name already exists", + "description": "The name of the tag already exists" + } + }, + "CreateNewsletterErrorCode": { + "username_already_exists": { + "variant": "error", + "name": "Username already exists", + "description": "A newsletter already exists with that username" + } + }, + "SubscriberSource": { + "api": { + "name": "API", + "variant": "info" + }, + "import": { + "variant": "info" + }, + "organic": { + "variant": "info" + }, + "admin": { + "variant": "info" + }, + "user": { + "variant": "info" + } + }, + "UpdateSubscriberErrorCode": { + "invalid_tag": { + "variant": "error", + "name": "Invalid tag" + } + }, + "ListSubscribersErrorCode": { + "invalid_tag": { + "variant": "error", + "name": "Invalid tag" + } + }, + "EmailCreationErrorCode": { + "subject_invalid": { + "variant": "error", + "name": "Invalid subject" + }, + "email_duplicate": { + "variant": "error", + "name": "Duplicate email" + }, + "email_invalid": { + "variant": "error", + "name": "Invalid email" + } + }, + "EmailStatus": { + "draft": { + "variant": "info", + "name": "Draft" + }, + "about_to_send": { + "variant": "info", + "name": "About to send" + }, + "scheduled": { + "variant": "info", + "name": "Scheduled" + }, + "in_flight": { + "variant": "info", + "name": "In flight" + }, + "imported": { + "variant": "info", + "name": "Imported" + }, + "deleted": { + "variant": "warning", + "name": "Deleted" + }, + "sent": { + "variant": "success", + "name": "Sent" + }, + "errored": { + "variant": "error", + "name": "Errored" + } + }, + "EmailType": { + "public": { + "variant": "info", + "name": "Public", + "description": "Public emails are sent out to all of your subscribers and are available in your web archives." + }, + "private": { + "variant": "info", + "name": "Private", + "description": "Private emails are sent out to all of your subscribers but are not viewable in your web archives." + }, + "premium": { + "variant": "info", + "name": "Premium", + "description": "Premium emails are sent out to only paying subscribers (including those with gift subscriptions or on a free trial), and only premium subscribers can view them in online archives." + }, + "free": { + "variant": "info", + "name": "Free", + "description": "Free emails are sent out only to subscribers who are not paying for your newsletter (so you can send specific emails to convince them to pay, for instance!)" + } + }, + "ExportCollection": { + "subscribers": { + "variant": "info", + "name": "Subscribers" + }, + "emails": { + "variant": "info", + "name": "Emails" + }, + "scheduled_emails": { + "variant": "info", + "name": "Scheduled emails" + }, + "drafts": { + "variant": "info", + "name": "Drafts" + }, + "unsubscribers": { + "variant": "info", + "name": "Unsubscribers" + }, + "events": { + "variant": "info", + "name": "Events" + }, + "referrals": { + "variant": "info", + "name": "Referrals" + } + }, + "ExportStatus": { + "not_started": { + "variant": "info", + "name": "Not started", + "description": "The export has not yet started" + }, + "in_progress": { + "variant": "info", + "name": "In progress", + "description": "The export is currently being processed" + }, + "ready": { + "variant": "success", + "description": "The export has completed" + }, + "error": { + "variant": "error", + "description": "The export was unable to be completed. Buttondown is looking into it." + } + }, "EventType": { "subscriber.created": { "name": "subscriber.created", @@ -67,6 +231,11 @@ "name": "Apply tags", "description": "This action requires two additional parameters within `metadata`: `tag_id` (the ID of the tag which you'd like to either add or remove to the list of subscribers) and `action` (which can be either `add` or `remove`)." }, + "apply_metadata": { + "variant": "info", + "name": "Apply metadata (to subscribers)", + "description": "This action requires two additional parameters within `metadata`: `action` (which can be either `add`, `set`, or `remove`), and `metadata` (the value of the metadata which you'd like to either add or remove to the list of subscribers)." + }, "ban_subscribers": { "variant": "info", "name": "Ban subscribers" diff --git a/lib/openapi/fixtures.json b/lib/openapi/fixtures.json new file mode 100644 index 0000000..287788b --- /dev/null +++ b/lib/openapi/fixtures.json @@ -0,0 +1,142 @@ +{ + "Subscriber": [ + { + "description": "Basic subscriber", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "email": "user@example.com", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "metadata": { + "foo": "bar" + }, + "notes": "", + "referrer_url": "http://jmduke.com/", + "secondary_id": 3, + "source": "api", + "subscriber_type": "regular", + "tags": [], + "utm_campaign": "", + "utm_medium": "", + "utm_source": "" + } + } + ], + "Export": [ + { + "description": "Basic export", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T15:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "url": "s3://buttondown/path-to-export", + "status": "completed" + } + } + ], + "Analytics": [ + { + "description": "Basic analytics response", + "object": { + "recipients": 100, + "deliveries": 99, + "opens": 50, + "clicks": 25, + "temporary_failures": 1, + "permanent_failures": 2, + "unsubscriptions": 3, + "complaints": 1 + } + } + ], + "Email": [ + { + "description": "Basic email", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "body": "Lorem ipsum yadda yadda", + "subject": "This is my first email on Buttondown!", + "excluded_tags": [], + "included_tags": [], + "email_type": "public", + "metadata": {}, + "secondary_id": 3, + "external_url": "https://buttondown.email/jmduke/my-first-email" + } + } + ], + "Newsletter": [ + { + "description": "Basic newsletter", + "object": { + "api_key": "7f819f8f-8220-4dcd-b7e3-37c81ead8b7a", + "creation_date": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "username": "string" + } + } + ], + "Image": [ + { + "description": "Basic image", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "image": "string" + } + } + ], + "Tag": [ + { + "description": "Basic tag", + "object": { + "color": "string", + "creation_date": "2019-08-24T14:15:22Z", + "description": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string" + } + } + ], + "BulkAction": [ + { + "description": "Basic bulk action", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T14:17:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "status": "completed", + "type": "delete_subscribers", + "metadata": { + "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + } + } + }, + { + "description": "Bulk action to apply tags (which has a slightly different payload)", + "object": { + "creation_date": "2019-08-24T14:15:22Z", + "completion_date": "2019-08-24T14:17:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "status": "completed", + "type": "apply_tags", + "metadata": { + "tag": "4931-4f5c-9b5b-2c9f6c9f6c9f", + "action": "add", + "ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + } + } + } + ], + "ErrorMessage": [ + { + "description": "Basic error", + "object": { + "code": "something_went_wrong", + "detail": "Your call is very important to us." + } + } + ] +} diff --git a/lib/openapi.json b/lib/openapi/openapi.json similarity index 92% rename from lib/openapi.json rename to lib/openapi/openapi.json index d0dbc06..c84adb9 100644 --- a/lib/openapi.json +++ b/lib/openapi/openapi.json @@ -17,7 +17,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ExportOutput" + "$ref": "#/components/schemas/Export" } } } @@ -75,7 +75,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Page_ExportOutput_" + "$ref": "#/components/schemas/Page_Export_" } } } @@ -108,16 +108,16 @@ ] } }, - "/exports/{export_id}": { + "/exports/{pk}": { "get": { "operationId": "api_views_exports_retrieve_export", "summary": "Retrieve Export", "parameters": [ { "in": "path", - "name": "export_id", + "name": "pk", "schema": { - "title": "Export Id", + "title": "Pk", "type": "string" }, "required": true @@ -129,7 +129,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ExportOutput" + "$ref": "#/components/schemas/Export" } } } @@ -163,7 +163,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TagOutput" + "$ref": "#/components/schemas/Tag" } } } @@ -215,7 +215,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Page_TagOutput_" + "$ref": "#/components/schemas/Page_Tag_" } } } @@ -249,7 +249,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TagOutput" + "$ref": "#/components/schemas/Tag" } } } @@ -281,7 +281,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TagOutput" + "$ref": "#/components/schemas/Tag" } } } @@ -367,7 +367,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ImageOutput" + "$ref": "#/components/schemas/Image" } } } @@ -386,9 +386,7 @@ "format": "binary" } }, - "required": [ - "image" - ] + "required": ["image"] } } }, @@ -439,7 +437,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmailOutput" + "$ref": "#/components/schemas/Email" } } } @@ -489,11 +487,7 @@ "in": "query", "name": "status", "schema": { - "default": [ - "about_to_send", - "in_flight", - "sent" - ], + "default": ["about_to_send", "in_flight", "sent"], "type": "array", "items": { "$ref": "#/components/schemas/EmailStatus" @@ -534,7 +528,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Page_EmailOutput_" + "$ref": "#/components/schemas/Page_Email_" } } } @@ -588,7 +582,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmailOutput" + "$ref": "#/components/schemas/Email" } } } @@ -666,7 +660,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SubscriberOutput" + "$ref": "#/components/schemas/Subscriber" } } } @@ -756,7 +750,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Page_SubscriberOutput_" + "$ref": "#/components/schemas/Page_Subscriber_" } } } @@ -810,7 +804,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SubscriberOutput" + "$ref": "#/components/schemas/Subscriber" } } } @@ -877,7 +871,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SubscriberOutput" + "$ref": "#/components/schemas/Subscriber" } } } @@ -1024,7 +1018,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Page_NewsletterOutput_" + "$ref": "#/components/schemas/Page_Newsletter_" } } } @@ -1066,7 +1060,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NewsletterOutput" + "$ref": "#/components/schemas/Newsletter" } } } @@ -1130,7 +1124,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NewsletterOutput" + "$ref": "#/components/schemas/Newsletter" } } } @@ -1302,16 +1296,12 @@ "ExportStatus": { "title": "Status", "description": "An enumeration.", - "enum": [ - "error", - "in_progress", - "not_started", - "ready" - ], + "enum": ["error", "in_progress", "not_started", "ready"], "type": "string" }, - "ExportOutput": { - "title": "ExportOutput", + "Export": { + "title": "Export", + "description": "Some software applications may want programmatic access to their newsletter exports.\nThis assists with a few niche use cases, such as regular backups or data ingestion\n(into a data warehouse), or post-publishing processes that hinge on email events.\n\nIn general, you probably won't _need_ to use this endpoint unless you\n _absolutely_ need to use this endpoint.", "type": "object", "properties": { "id": { @@ -1337,12 +1327,7 @@ "format": "date-time" } }, - "required": [ - "id", - "creation_date", - "status", - "url" - ] + "required": ["id", "creation_date", "status", "url"] }, "ErrorMessage": { "title": "ErrorMessage", @@ -1364,9 +1349,7 @@ } } }, - "required": [ - "detail" - ] + "required": ["detail"] }, "ExportCollection": { "title": "Collection", @@ -1393,19 +1376,17 @@ } } }, - "required": [ - "collections" - ] + "required": ["collections"] }, - "Page_ExportOutput_": { - "title": "Page[ExportOutput]", + "Page_Export_": { + "title": "Page[Export]", "type": "object", "properties": { "results": { "title": "Results", "type": "array", "items": { - "$ref": "#/components/schemas/ExportOutput" + "$ref": "#/components/schemas/Export" } }, "next": { @@ -1421,13 +1402,10 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, - "TagOutput": { - "title": "TagOutput", + "Tag": { + "title": "Tag", "type": "object", "properties": { "id": { @@ -1457,13 +1435,7 @@ "format": "date-time" } }, - "required": [ - "id", - "name", - "color", - "secondary_id", - "creation_date" - ] + "required": ["id", "name", "color", "secondary_id", "creation_date"] }, "TagInput": { "title": "TagInput", @@ -1482,20 +1454,17 @@ "type": "string" } }, - "required": [ - "name", - "color" - ] + "required": ["name", "color"] }, - "Page_TagOutput_": { - "title": "Page[TagOutput]", + "Page_Tag_": { + "title": "Page[Tag]", "type": "object", "properties": { "results": { "title": "Results", "type": "array", "items": { - "$ref": "#/components/schemas/TagOutput" + "$ref": "#/components/schemas/Tag" } }, "next": { @@ -1511,17 +1480,12 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "UpdateTagErrorCode": { "title": "UpdateTagErrorCode", "description": "An enumeration.", - "enum": [ - "name_already_exists" - ], + "enum": ["name_already_exists"], "type": "string" }, "ErrorMessage_UpdateTagErrorCode_": { @@ -1544,10 +1508,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "TagUpdateInput": { "title": "TagUpdateInput", @@ -1567,8 +1528,8 @@ } } }, - "ImageOutput": { - "title": "ImageOutput", + "Image": { + "title": "Image", "type": "object", "properties": { "id": { @@ -1586,23 +1547,12 @@ "type": "string" } }, - "required": [ - "id", - "creation_date", - "image" - ] + "required": ["id", "creation_date", "image"] }, "EmailType": { "title": "Type", "description": "An enumeration.", - "enum": [ - "public", - "private", - "premium", - "free", - "archival", - "hidden" - ], + "enum": ["public", "private", "premium", "free", "archival", "hidden"], "type": "string" }, "EmailStatus": { @@ -1620,8 +1570,8 @@ ], "type": "string" }, - "EmailOutput": { - "title": "EmailOutput", + "Email": { + "title": "Email", "type": "object", "properties": { "id": { @@ -1698,11 +1648,7 @@ "EmailCreationErrorCode": { "title": "EmailCreationErrorCode", "description": "An enumeration.", - "enum": [ - "subject_invalid", - "email_duplicate", - "email_invalid" - ], + "enum": ["subject_invalid", "email_duplicate", "email_invalid"], "type": "string" }, "ErrorMessage_EmailCreationErrorCode_": { @@ -1725,10 +1671,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "EmailInput": { "title": "EmailInput", @@ -1805,19 +1748,17 @@ } } }, - "required": [ - "subject" - ] + "required": ["subject"] }, - "Page_EmailOutput_": { - "title": "Page[EmailOutput]", + "Page_Email_": { + "title": "Page[Email]", "type": "object", "properties": { "results": { "title": "Results", "type": "array", "items": { - "$ref": "#/components/schemas/EmailOutput" + "$ref": "#/components/schemas/Email" } }, "next": { @@ -1833,10 +1774,7 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "Analytics": { "title": "Analytics", @@ -1925,8 +1863,8 @@ ], "type": "string" }, - "SubscriberOutput": { - "title": "SubscriberOutput", + "Subscriber": { + "title": "Subscriber", "type": "object", "properties": { "id": { @@ -2033,19 +1971,17 @@ "type": "string" } }, - "required": [ - "email" - ] + "required": ["email"] }, - "Page_SubscriberOutput_": { - "title": "Page[SubscriberOutput]", + "Page_Subscriber_": { + "title": "Page[Subscriber]", "type": "object", "properties": { "results": { "title": "Results", "type": "array", "items": { - "$ref": "#/components/schemas/SubscriberOutput" + "$ref": "#/components/schemas/Subscriber" } }, "next": { @@ -2061,17 +1997,12 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "ListSubscribersErrorCode": { "title": "ListSubscribersErrorCode", "description": "An enumeration.", - "enum": [ - "invalid_tag" - ], + "enum": ["invalid_tag"], "type": "string" }, "ErrorMessage_ListSubscribersErrorCode_": { @@ -2094,10 +2025,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "UpdateSubscriberErrorCode": { "title": "UpdateSubscriberErrorCode", @@ -2129,10 +2057,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "SubscriberUpdateInput": { "title": "SubscriberUpdateInput", @@ -2167,8 +2092,8 @@ } } }, - "NewsletterOutput": { - "title": "NewsletterOutput", + "Newsletter": { + "title": "Newsletter", "type": "object", "properties": { "id": { @@ -2208,15 +2133,15 @@ "api_key" ] }, - "Page_NewsletterOutput_": { - "title": "Page[NewsletterOutput]", + "Page_Newsletter_": { + "title": "Page[Newsletter]", "type": "object", "properties": { "results": { "title": "Results", "type": "array", "items": { - "$ref": "#/components/schemas/NewsletterOutput" + "$ref": "#/components/schemas/Newsletter" } }, "next": { @@ -2232,17 +2157,12 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "CreateNewsletterErrorCode": { "title": "CreateNewsletterErrorCode", "description": "An enumeration.", - "enum": [ - "username_already_exists" - ], + "enum": ["username_already_exists"], "type": "string" }, "ErrorMessage_CreateNewsletterErrorCode_": { @@ -2265,10 +2185,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "NewsletterInput": { "title": "NewsletterInput", @@ -2287,11 +2204,7 @@ "type": "string" } }, - "required": [ - "username", - "name", - "description" - ] + "required": ["username", "name", "description"] }, "NewsletterUpdateInput": { "title": "NewsletterUpdateInput", @@ -2314,12 +2227,7 @@ "BulkActionStatus": { "title": "Status", "description": "\n Represents the status of a bulk action.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", - "enum": [ - "not_started", - "in_progress", - "processed", - "failed" - ], + "enum": ["not_started", "in_progress", "processed", "failed"], "type": "string" }, "BulkActionType": { @@ -2327,6 +2235,7 @@ "description": "\n Represents the action being performed on a bulk of objects.\n\n (Not to be coy, but these names should be self-explanatory.)\n ", "enum": [ "apply_tags", + "apply_metadata", "ban_subscribers", "delete_emails", "delete_subscribers", @@ -2388,13 +2297,7 @@ } } }, - "required": [ - "id", - "creation_date", - "status", - "type", - "metadata" - ] + "required": ["id", "creation_date", "status", "type", "metadata"] }, "BulkActionInput": { "title": "BulkActionInput", @@ -2424,10 +2327,7 @@ } } }, - "required": [ - "type", - "metadata" - ] + "required": ["type", "metadata"] } }, "securitySchemes": { @@ -2437,4 +2337,4 @@ } } } -} \ No newline at end of file +} diff --git a/lib/openapi/types.ts b/lib/openapi/types.ts new file mode 100644 index 0000000..d186123 --- /dev/null +++ b/lib/openapi/types.ts @@ -0,0 +1,21 @@ +import OpenAPI from "./openapi.json"; +import OpenAPIEnums from "./enums.json"; +import OpenAPIFixtures from "./fixtures.json"; + +type KeysOfType = keyof { + [P in keyof T as V extends keyof T[P] ? P : never]: any; +}; + +export type Route = keyof typeof OpenAPI.paths; +export type Method = keyof typeof OpenAPI.paths[R] & string; +export type Operation< + R extends Route, + M extends Method +> = typeof OpenAPI.paths[R][M]; +export type Enum = keyof typeof OpenAPI.components.schemas & + keyof typeof OpenAPIEnums; +export type Object = KeysOfType< + typeof OpenAPI.components.schemas, + "properties" +> & + KeysOfType; diff --git a/lib/openapi/utils.ts b/lib/openapi/utils.ts new file mode 100644 index 0000000..f8d5c9b --- /dev/null +++ b/lib/openapi/utils.ts @@ -0,0 +1,60 @@ +import OpenAPI from "./openapi.json"; +import { Method, Operation, Route } from "./types"; + +export const extractRefFromType = ( + type: string +): keyof typeof OpenAPI.components.schemas | null => { + // If #/components/schemas/ is present, extract the name of the schema + const match = type.match(/#\/components\/schemas\/(.*)/); + if (match) { + const ref = match[1]; + return ref as keyof typeof OpenAPI.components.schemas; + } + return null; +}; + +export const extractBackingFixtureFromRef = ( + ref: string +): { + type?: "Page" | "ErrorMessage"; + value: string; +} => { + // Pages are wrapped like so: "Page_FOO_". + // We want to extract 'FOO'. + if (ref.startsWith("Page_")) { + const pageName = ref.split("_")[1]; + return { + type: "Page", + value: pageName, + }; + } + + // Error messages are wrapped like so: "ErrorMessage_FOO_". + // We want to extract 'FOO'. + if (ref.startsWith("ErrorMessage_")) { + const errorMessageName = ref.split("_")[1]; + return { + type: "ErrorMessage", + value: errorMessageName, + }; + } + + return { + value: ref, + }; +}; + +export const extractOperation = ( + route: R, + method: Method +): Operation> => { + return OpenAPI.paths[route][method]; +}; + +export const extractRouteInformation = >( + route: R, + method: M +) => { + const operation = extractOperation(route, method); + return operation.summary; +}; From c3034fe79ef7201b4bb2513327622ff6242b6e34 Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 11:40:00 -0500 Subject: [PATCH 06/11] Try to fix almost everyting --- components/MonospacedSpan.tsx | 3 + components/Sidebar/NavigationHierarchy.tsx | 1 + components/Table.tsx | 2 +- components/api/Endpoint.tsx | 16 +- components/api/EndpointDescription.tsx | 56 ++-- components/api/ObjectDescription.tsx | 109 +++---- components/api/PathTable.tsx | 45 ++- components/api/ResponsesTable.tsx | 36 ++- components/api/openapi/EnumTable.tsx | 11 + lib/openapi/openapi.json | 201 +++++++++--- package-lock.json | 12 +- pages/api-reference/bulk-actions.mdx | 11 - pages/api-reference/emails/index.mdx | 176 +--------- pages/api-reference/emails/metadata.json | 17 - pages/api-reference/events-and-webhooks.mdx | 2 +- pages/api-reference/exports/index.mdx | 52 +-- pages/api-reference/exports/metadata.json | 5 - pages/api-reference/images/index.mdx | 102 +----- pages/api-reference/images/metadata.json | 10 - pages/api-reference/newsletters/index.mdx | 141 +------- pages/api-reference/newsletters/metadata.json | 13 - pages/api-reference/subscribers/index.mdx | 305 +----------------- pages/api-reference/subscribers/metadata.json | 5 - pages/api-reference/tags/index.mdx | 110 +------ pages/api-reference/tags/metadata.json | 12 - .../sending-your-first-email.mdx | 2 +- public/search-results.json | 12 +- 27 files changed, 358 insertions(+), 1109 deletions(-) create mode 100644 components/MonospacedSpan.tsx create mode 100644 components/api/openapi/EnumTable.tsx delete mode 100644 pages/api-reference/emails/metadata.json delete mode 100644 pages/api-reference/exports/metadata.json delete mode 100644 pages/api-reference/images/metadata.json delete mode 100644 pages/api-reference/newsletters/metadata.json delete mode 100644 pages/api-reference/subscribers/metadata.json delete mode 100644 pages/api-reference/tags/metadata.json diff --git a/components/MonospacedSpan.tsx b/components/MonospacedSpan.tsx new file mode 100644 index 0000000..e71361b --- /dev/null +++ b/components/MonospacedSpan.tsx @@ -0,0 +1,3 @@ +export default function MonospacedSpan(s: string | JSX.Element) { + return {s}; +} diff --git a/components/Sidebar/NavigationHierarchy.tsx b/components/Sidebar/NavigationHierarchy.tsx index b9cc9ec..56994a5 100644 --- a/components/Sidebar/NavigationHierarchy.tsx +++ b/components/Sidebar/NavigationHierarchy.tsx @@ -167,6 +167,7 @@ const NAVIGATION: Array = [ { name: "Images", href: "/api-reference/images" }, { name: "Newsletters", href: "/api-reference/newsletters" }, { name: "Exports", href: "/api-reference/exports" }, + { name: "Bulk actions", href: "/api-reference/bulk-actions" }, ], }, { diff --git a/components/Table.tsx b/components/Table.tsx index 2104865..b24ba19 100644 --- a/components/Table.tsx +++ b/components/Table.tsx @@ -8,7 +8,7 @@ export type Column = { }; export type Row = { - [key: string]: string; + [key: string]: string | undefined; }; type TableProps = { diff --git a/components/api/Endpoint.tsx b/components/api/Endpoint.tsx index 63b40d2..e3d24f8 100644 --- a/components/api/Endpoint.tsx +++ b/components/api/Endpoint.tsx @@ -1,14 +1,20 @@ +import { Method, Route } from "../../lib/openapi/types"; import { H2 } from "../Markdown"; import ClosedBetaNotice from "./ClosedBetaNotice"; -type Props = { +type Props = { title: string; - method: string; + method: Method; path: string; beta?: boolean; }; -export default function Endpoint({ title, method, path, beta }: Props) { +export default function Endpoint({ + title, + method, + path, + beta, +}: Props) { return ( <>

{title}

@@ -19,7 +25,9 @@ export default function Endpoint({ title, method, path, beta }: Props) { )}
-        {method} → https://api.buttondown.email/v1{path}
+        $ curl -X{" "}
+        {method.toLocaleUpperCase()} https://api.buttondown.email/v1
+        {path}
       
); diff --git a/components/api/EndpointDescription.tsx b/components/api/EndpointDescription.tsx index fa0f02c..a6987be 100644 --- a/components/api/EndpointDescription.tsx +++ b/components/api/EndpointDescription.tsx @@ -1,37 +1,45 @@ import Endpoint from "./Endpoint"; import ParametersTable from "./ParametersTable"; import PathTable from "./PathTable"; -import OpenAPI from "../../lib/openapi.json"; -import { extractRefFromType } from "../../lib/openapi-utils"; +import OpenAPI from "../../lib/openapi/openapi.json"; +import { Method, Route, Operation } from "../../lib/openapi/types"; +import { extractRefFromType, extractOperation } from "../../lib/openapi/utils"; -type Path = keyof typeof OpenAPI.paths; -type Operation = { - summary: string; - parameters: any[]; - responses: any; - requestBody?: { - content: { - "application/json": { - schema: { - $ref: string; - }; - }; - }; - }; -}; +// type Path = keyof typeof OpenAPI.paths; +// type Operation = { +// summary: string; +// parameters: any[]; +// responses: any; +// requestBody?: { +// content: { +// "application/json": { +// schema: { +// $ref: string; +// }; +// }; +// }; +// }; +// }; -export default function EndpointDescription({ path }: { path: Path }) { +export default function EndpointDescription({ + path, +}: { + path: R; +}) { const informationForPath = OpenAPI.paths[path]; - const methods = Object.keys( - informationForPath - ) as (keyof typeof informationForPath)[]; + const methods = Object.keys(informationForPath) as Method[]; return ( <> {methods.map((method) => { - const operation = informationForPath[method] as Operation; - const parameters = - operation.requestBody?.content["application/json"].schema.$ref; + const operation = extractOperation(path, method) as any; + const body = operation.requestBody; + const parameters = body + ? ( + body.content["application/json"] || + body.content["multipart/form-data"] + ).schema.$ref + : undefined; const ref = parameters !== undefined ? extractRefFromType(parameters) : null; diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index eb6bb87..bb602e2 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -1,15 +1,19 @@ import { Code, H4, P } from "../Markdown"; import Table from "../Table"; -import { extractRefFromType } from "../../lib/openapi-utils"; +import { extractRefFromType } from "../../lib/openapi/utils"; import EnumTable from "./EnumTable"; -import OpenAPI from "../../lib/openapi.json"; -import OpenAPIEnums from "../../lib/enums.json"; -import OpenAPIFixtures from "../../lib/fixtures.json"; +import OpenAPI from "../../lib/openapi/openapi.json"; +import OpenAPIEnums from "../../lib/openapi/enums.json"; +import OpenAPIFixtures from "../../lib/openapi/fixtures.json"; import EndpointDescription from "./EndpointDescription"; +import { Enum, Object as OpenAPIObject, Route } from "../../lib/openapi/types"; +import MonospacedSpan from "../MonospacedSpan"; -function MonospacedSpan(s: string | JSX.Element) { - return {s}; -} +type Props = { + name: OpenAPIObject; + enums: Array; + endpoints: Array; +}; type Field = { field: string; @@ -17,25 +21,6 @@ type Field = { description: string; }; -type KeysOfType = keyof { - [P in keyof T as V extends keyof T[P] ? P : never]: any; -}; - -type SchemaElementWithProperties = KeysOfType< - typeof OpenAPI.components.schemas, - "properties" -> & - KeysOfType; - -type Props = { - name: SchemaElementWithProperties & keyof typeof OpenAPIFixtures; - fields: Array; - enums: Array< - keyof typeof OpenAPIEnums & keyof typeof OpenAPI.components.schemas - >; - endpoints: Array; -}; - export default function ObjectDescription({ name, enums, endpoints }: Props) { const schema = OpenAPI.components.schemas[name]; const fields = Object.entries(schema.properties).map(([key, property]) => { @@ -50,21 +35,21 @@ export default function ObjectDescription({ name, enums, endpoints }: Props) { const fixtures = OpenAPIFixtures[name]; return ( -
-

{schema.description}

- {fixtures.length > 0 && - fixtures.map((fixture) => { - return ( - <> -

{fixture.description}

- - {JSON.stringify(fixture.object, null, 4)} - - - ); - })} - -
+
+
+

{schema.description}

+ {fixtures.length > 0 && + fixtures.map((fixture) => { + return ( + <> +

{fixture.description}

+ + {JSON.stringify(fixture.object, null, 4)} + + + ); + })} +
- {enums !== undefined && - enums.map((e) => { - return ( -
- -

- {OpenAPI.components.schemas[e].title} ({MonospacedSpan(e)}) -

-

{OpenAPI.components.schemas[e].description}

-
- -
- ); - })} + {enums.map((e) => { + return ( +
+ +

+ {OpenAPI.components.schemas[e].title} ({MonospacedSpan(e)}) +

+

{OpenAPI.components.schemas[e].description}

+
+ +
+ ); + })} - {endpoints !== undefined && - endpoints.map((e) => { - return ( -
- -
- ); - })} + {endpoints.map((e) => { + return ( +
+ +
+ ); + })} ); } diff --git a/components/api/PathTable.tsx b/components/api/PathTable.tsx index 690d868..dc3d0d9 100644 --- a/components/api/PathTable.tsx +++ b/components/api/PathTable.tsx @@ -1,13 +1,42 @@ import ResponsesTable from "./ResponsesTable"; +import { extractBackingFixtureFromRef } from "../../lib/openapi/utils"; +import { H3 } from "../Markdown"; +import EnumTable from "./openapi/EnumTable"; + +const extractSchemaFromContent = (content: any): string | undefined => { + if (content) { + const schema = content["application/json"].schema; + if (schema) { + return schema["$ref"].split("/").pop(); + } + } +}; export default function PathTable({ content }: any) { - return ( - ({ - Status: key, - description: content[key].description, - "Sample Response": content[key].content["application/json"].schema, - }))} - /> + const responses = Object.keys(content).map((key) => ({ + Status: key, + description: content[key].description, + fixture: extractSchemaFromContent(content[key].content), + })); + const responseWithDistinctErrorType = responses.find((r) => + r.fixture?.startsWith("ErrorMessage_") ); + if (responseWithDistinctErrorType) { + return ( + <> + +

Error codes

+ + + + + ); + } + return ; } diff --git a/components/api/ResponsesTable.tsx b/components/api/ResponsesTable.tsx index a1055a2..7bdd02e 100644 --- a/components/api/ResponsesTable.tsx +++ b/components/api/ResponsesTable.tsx @@ -1,7 +1,8 @@ import classNames from "../../lib/classNames"; import { Code, H3 } from "../Markdown"; -import Table, { Row } from "../Table"; -import OpenAPIFixtures from "../../lib/fixtures.json"; +import Table from "../Table"; +import OpenAPIFixtures from "../../lib/openapi/fixtures.json"; +import { extractBackingFixtureFromRef } from "../../lib/openapi/utils"; function ResponseCodeBadge(text: string) { return ( @@ -21,30 +22,29 @@ function ResponseCodeBadge(text: string) { } const fixtureForRef = (ref: string) => { - // Pages are wrapped like so: "Page_FOO_". - // We want to extract 'FOO'. - if (ref.startsWith("Page_")) { - const pageName = ref.split("_")[1]; - const pageRef = OpenAPIFixtures[pageName][0].object; + const fixtureInformation = extractBackingFixtureFromRef(ref); + if (fixtureInformation.type === "Page") { + const pageRef = OpenAPIFixtures[fixtureInformation.value][0].object; return { results: [pageRef], count: 1, }; + } else if (fixtureInformation.type === "ErrorMessage") { + return OpenAPIFixtures["ErrorMessage"][0].object; + } else { + return OpenAPIFixtures[fixtureInformation.value][0].object; } - return OpenAPIFixtures[ref][0].object || ref; }; -function SampleResponse(text: any) { - const relevantText = text["Sample Response"]["$ref"] - ? fixtureForRef(text["Sample Response"]["$ref"].split("/").pop()) - : text["Sample Response"]; +function SampleResponse(fixtureName: string) { + const relevantText = fixtureForRef(fixtureName); return {JSON.stringify(relevantText, null, 4)}; } type Response = { Status: string; description: string; - "Sample Response": string; + fixture: string | undefined; }; type Props = { @@ -62,7 +62,15 @@ export default function ResponsesTable({ content }: Props) { component: (c: Response) => ResponseCodeBadge(c.Status), }, { title: "Description", key: "description" }, - { title: "Sample Response", component: SampleResponse }, + { + title: "Sample Response", + component: (c: Response) => + c.fixture ? ( + SampleResponse(c.fixture) + ) : ( + {"{}"} + ), + }, ]} content={content} /> diff --git a/components/api/openapi/EnumTable.tsx b/components/api/openapi/EnumTable.tsx new file mode 100644 index 0000000..b852d34 --- /dev/null +++ b/components/api/openapi/EnumTable.tsx @@ -0,0 +1,11 @@ +import OpenAPIEnums from "../../../lib/openapi/enums.json"; +import OldEnumTable from "../EnumTable"; + +type props = { + enum: keyof typeof OpenAPIEnums; +}; + +export default function EnumTable({ enum: enumName }: props) { + const enumValues = OpenAPIEnums[enumName]; + return ; +} diff --git a/lib/openapi/openapi.json b/lib/openapi/openapi.json index c84adb9..c3ff0a7 100644 --- a/lib/openapi/openapi.json +++ b/lib/openapi/openapi.json @@ -386,7 +386,9 @@ "format": "binary" } }, - "required": ["image"] + "required": [ + "image" + ] } } }, @@ -487,7 +489,11 @@ "in": "query", "name": "status", "schema": { - "default": ["about_to_send", "in_flight", "sent"], + "default": [ + "about_to_send", + "in_flight", + "sent" + ], "type": "array", "items": { "$ref": "#/components/schemas/EmailStatus" @@ -1295,8 +1301,13 @@ "schemas": { "ExportStatus": { "title": "Status", - "description": "An enumeration.", - "enum": ["error", "in_progress", "not_started", "ready"], + "description": "\n Represents the status of an export.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", + "enum": [ + "error", + "in_progress", + "not_started", + "ready" + ], "type": "string" }, "Export": { @@ -1327,7 +1338,11 @@ "format": "date-time" } }, - "required": ["id", "creation_date", "status", "url"] + "required": [ + "id", + "creation_date", + "status" + ] }, "ErrorMessage": { "title": "ErrorMessage", @@ -1349,11 +1364,13 @@ } } }, - "required": ["detail"] + "required": [ + "detail" + ] }, "ExportCollection": { "title": "Collection", - "description": "An enumeration.", + "description": "\n A group of data that can be exported in an export.\n ", "enum": [ "subscribers", "emails", @@ -1376,7 +1393,9 @@ } } }, - "required": ["collections"] + "required": [ + "collections" + ] }, "Page_Export_": { "title": "Page[Export]", @@ -1402,10 +1421,14 @@ "type": "integer" } }, - "required": ["results", "count"] + "required": [ + "results", + "count" + ] }, "Tag": { "title": "Tag", + "description": "Tags are a way to organize your subscribers. You can create, update, and\ndelete tags via the API. You can also list all tags for a given newsletter.\n\nTags don't have any strict functionality on their own, but you can send emails\nto subscribers with a given tag (or to all subscribers _without_ a given tag.)", "type": "object", "properties": { "id": { @@ -1435,7 +1458,13 @@ "format": "date-time" } }, - "required": ["id", "name", "color", "secondary_id", "creation_date"] + "required": [ + "id", + "name", + "color", + "secondary_id", + "creation_date" + ] }, "TagInput": { "title": "TagInput", @@ -1454,7 +1483,10 @@ "type": "string" } }, - "required": ["name", "color"] + "required": [ + "name", + "color" + ] }, "Page_Tag_": { "title": "Page[Tag]", @@ -1480,12 +1512,17 @@ "type": "integer" } }, - "required": ["results", "count"] + "required": [ + "results", + "count" + ] }, "UpdateTagErrorCode": { "title": "UpdateTagErrorCode", - "description": "An enumeration.", - "enum": ["name_already_exists"], + "description": "\n A potential error code that can be returned when updating a tag.\n ", + "enum": [ + "name_already_exists" + ], "type": "string" }, "ErrorMessage_UpdateTagErrorCode_": { @@ -1508,7 +1545,10 @@ } } }, - "required": ["code", "detail"] + "required": [ + "code", + "detail" + ] }, "TagUpdateInput": { "title": "TagUpdateInput", @@ -1530,6 +1570,7 @@ }, "Image": { "title": "Image", + "description": "Images are, well, images! Buttondown allows you to upload images to its secure\nS3 bucket and do with them what you will. This is sort of an odd duck of an\nAPI, to be sure, but if you want to be able to do things like draft\nand send emails completely on your iPad you need a surefire way of creating images.", "type": "object", "properties": { "id": { @@ -1547,17 +1588,28 @@ "type": "string" } }, - "required": ["id", "creation_date", "image"] + "required": [ + "id", + "creation_date", + "image" + ] }, "EmailType": { "title": "Type", - "description": "An enumeration.", - "enum": ["public", "private", "premium", "free", "archival", "hidden"], + "description": "\n Represents the audience of an email, and to whom it is visible both in the initial\n email and in online archives.\n ", + "enum": [ + "public", + "private", + "premium", + "free", + "archival", + "hidden" + ], "type": "string" }, "EmailStatus": { "title": "Status", - "description": "An enumeration.", + "description": "\n Represents the state of an email.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", "enum": [ "draft", "about_to_send", @@ -1572,6 +1624,7 @@ }, "Email": { "title": "Email", + "description": "Emails are what you're for here on Buttondown at the end of the day, right?\nCreating an email via the API is just like creating one in the interface;\nit will instantly trigger sending actual emails,\nbased on the tags and email type you provide.", "type": "object", "properties": { "id": { @@ -1647,8 +1700,12 @@ }, "EmailCreationErrorCode": { "title": "EmailCreationErrorCode", - "description": "An enumeration.", - "enum": ["subject_invalid", "email_duplicate", "email_invalid"], + "description": "\n Represents the type of error that occurred when creating an email.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", + "enum": [ + "subject_invalid", + "email_duplicate", + "email_invalid" + ], "type": "string" }, "ErrorMessage_EmailCreationErrorCode_": { @@ -1671,7 +1728,10 @@ } } }, - "required": ["code", "detail"] + "required": [ + "code", + "detail" + ] }, "EmailInput": { "title": "EmailInput", @@ -1748,7 +1808,9 @@ } } }, - "required": ["subject"] + "required": [ + "subject" + ] }, "Page_Email_": { "title": "Page[Email]", @@ -1774,7 +1836,10 @@ "type": "integer" } }, - "required": ["results", "count"] + "required": [ + "results", + "count" + ] }, "Analytics": { "title": "Analytics", @@ -1826,7 +1891,7 @@ }, "SubscriberType": { "title": "Type", - "description": "An enumeration.", + "description": "\n Represents the state of a subscriber and what emails they\n should or should not be receiving. This type is meant to be fully expressive\n so as to consolidate the logic of determining what emails a subscriber should\n receive into a single place.\n ", "enum": [ "regular", "premium", @@ -1846,25 +1911,19 @@ }, "SubscriberSource": { "title": "Source", - "description": "An enumeration.", + "description": "\n Represents the original provenance of a subscriber. This value is not exposed\n to subscribers; it's only used for internal tracking purposes and governs some\n of the behavior of the subscriber (i.e. whether or not to require double\n opt-in.)\n ", "enum": [ "api", - "buttondown", - "csv", - "mailchimp", + "import", "organic", - "nouveau", - "substack", - "tinyletter", - "typeform", "user", - "admin", - "drip" + "admin" ], "type": "string" }, "Subscriber": { "title": "Subscriber", + "description": "Subscribers are the main way you collect email addresses and\nrecipients on Buttondown. They're what you see on your\n[subscribers page](https://buttondown.email/subscribers).", "type": "object", "properties": { "id": { @@ -1971,7 +2030,9 @@ "type": "string" } }, - "required": ["email"] + "required": [ + "email" + ] }, "Page_Subscriber_": { "title": "Page[Subscriber]", @@ -1997,12 +2058,17 @@ "type": "integer" } }, - "required": ["results", "count"] + "required": [ + "results", + "count" + ] }, "ListSubscribersErrorCode": { "title": "ListSubscribersErrorCode", - "description": "An enumeration.", - "enum": ["invalid_tag"], + "description": "\n Represents the type of error that occurred when listing subscribers.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", + "enum": [ + "invalid_tag" + ], "type": "string" }, "ErrorMessage_ListSubscribersErrorCode_": { @@ -2025,11 +2091,14 @@ } } }, - "required": ["code", "detail"] + "required": [ + "code", + "detail" + ] }, "UpdateSubscriberErrorCode": { "title": "UpdateSubscriberErrorCode", - "description": "An enumeration.", + "description": "\n Represents the type of error that occurred when updating a subscriber.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", "enum": [ "email_already_exists", "email_invalid", @@ -2057,7 +2126,10 @@ } } }, - "required": ["code", "detail"] + "required": [ + "code", + "detail" + ] }, "SubscriberUpdateInput": { "title": "SubscriberUpdateInput", @@ -2094,6 +2166,7 @@ }, "Newsletter": { "title": "Newsletter", + "description": "You will likely not need to interact with your newsletter settings\nprogrammatically, but if you do, this is the endpoint for you. You can\ncreate, update, and list newsletters via the API; this is ideal for\nintegrating with Buttondown as a headless email or newsletter provider\n(e.g. for a SaaS product.)", "type": "object", "properties": { "id": { @@ -2157,12 +2230,17 @@ "type": "integer" } }, - "required": ["results", "count"] + "required": [ + "results", + "count" + ] }, "CreateNewsletterErrorCode": { "title": "CreateNewsletterErrorCode", - "description": "An enumeration.", - "enum": ["username_already_exists"], + "description": "\n Represents the type of error that occurred when creating a newsletter.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", + "enum": [ + "username_already_exists" + ], "type": "string" }, "ErrorMessage_CreateNewsletterErrorCode_": { @@ -2185,7 +2263,10 @@ } } }, - "required": ["code", "detail"] + "required": [ + "code", + "detail" + ] }, "NewsletterInput": { "title": "NewsletterInput", @@ -2204,7 +2285,11 @@ "type": "string" } }, - "required": ["username", "name", "description"] + "required": [ + "username", + "name", + "description" + ] }, "NewsletterUpdateInput": { "title": "NewsletterUpdateInput", @@ -2227,7 +2312,12 @@ "BulkActionStatus": { "title": "Status", "description": "\n Represents the status of a bulk action.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", - "enum": ["not_started", "in_progress", "processed", "failed"], + "enum": [ + "not_started", + "in_progress", + "processed", + "failed" + ], "type": "string" }, "BulkActionType": { @@ -2297,7 +2387,13 @@ } } }, - "required": ["id", "creation_date", "status", "type", "metadata"] + "required": [ + "id", + "creation_date", + "status", + "type", + "metadata" + ] }, "BulkActionInput": { "title": "BulkActionInput", @@ -2327,7 +2423,10 @@ } } }, - "required": ["type", "metadata"] + "required": [ + "type", + "metadata" + ] } }, "securitySchemes": { @@ -2337,4 +2436,4 @@ } } } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 646677f..2c38fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8547,9 +8547,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001355", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001355.tgz", - "integrity": "sha512-Sd6pjJHF27LzCB7pT7qs+kuX2ndurzCzkpJl6Qct7LPSZ9jn0bkOA8mdgMgmqnQAWLVOOGjLpc+66V57eLtb1g==", + "version": "1.0.30001436", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", + "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==", "funding": [ { "type": "opencollective", @@ -30488,9 +30488,9 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, "caniuse-lite": { - "version": "1.0.30001355", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001355.tgz", - "integrity": "sha512-Sd6pjJHF27LzCB7pT7qs+kuX2ndurzCzkpJl6Qct7LPSZ9jn0bkOA8mdgMgmqnQAWLVOOGjLpc+66V57eLtb1g==" + "version": "1.0.30001436", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz", + "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==" }, "capture-exit": { "version": "2.0.0", diff --git a/pages/api-reference/bulk-actions.mdx b/pages/api-reference/bulk-actions.mdx index 78dd4ca..25291cd 100644 --- a/pages/api-reference/bulk-actions.mdx +++ b/pages/api-reference/bulk-actions.mdx @@ -4,18 +4,7 @@ description: An API reference for the 'bulk action' object in Buttondown --- import Layout from "../../components/Layout"; -import ResponsesTable from "../../components/api/ResponsesTable"; -import PathTable from "../../components/api/PathTable"; import ObjectDescription from "../../components/api/ObjectDescription"; -import ParametersTable from "../../components/api/ParametersTable"; -import Endpoint from "../../components/api/Endpoint"; -import EndpointDescription from "../../components/api/EndpointDescription"; -import EnumTable from "../../components/api/EnumTable"; -import OpenAPIEnums from "../../lib/enums.json"; - -import { PAGE_PARAMETER } from "../../lib/parameters"; -import OpenAPI from "../../lib/openapi.json"; -import OpenAPIFixtures from "../../lib/fixtures.json"; export const meta = { title: "Bulk actions", diff --git a/pages/api-reference/emails/index.mdx b/pages/api-reference/emails/index.mdx index 7c8b68b..21891f5 100644 --- a/pages/api-reference/emails/index.mdx +++ b/pages/api-reference/emails/index.mdx @@ -4,186 +4,18 @@ description: An API reference for the 'email' object in Buttondown --- import Layout from "../../../components/Layout"; -import Table from "../../../components/Table"; -import ResponsesTable from "../../../components/api/ResponsesTable"; import ObjectDescription from "../../../components/api/ObjectDescription"; -import ParametersTable from "../../../components/api/ParametersTable"; -import ClosedBetaNotice from "../../../components/api/ClosedBetaNotice"; -import Endpoint from "../../../components/api/Endpoint"; -import DATA from "./metadata.json"; -import { PAGE_PARAMETER } from "../../../lib/parameters"; export const meta = { - title: DATA.title, + title: "Emails", }; export default ({ children }) => {children}; # Emails -Emails are what you're for here on Buttondown at the end of the day, right? Creating an email via the API is just like creating one in the interface; it will instantly trigger sending actual emails, based on the tags and email type you provide. - -## The email object - -An email looks like this: - EmailType
}, - { field: "body", type: "string" }, - { field: "included_tags", type: "array | array" }, - { field: "excluded_tags", type: "array | array" }, - { field: "metadata", type: "map" }, - ]} -/> - - - -### Email types - -Email types govern the visibility of an email, both in the sense of to whom it is originally delivered and whom can view it -after it's been sent out. By default, all emails are public. - -
{s.type}, - }, - { title: "description" }, - ]} - content={[ - { - type: "public", - description: - "Public emails are sent out to all of your subscribers and are available in your web archives.", - }, - { - type: "private", - description: - "Private emails are sent out to all of your subscribers but are not viewable in your web archives.", - }, - { - type: "premium", - description: - "Premium emails are sent out to only paying subscribers (including those with gift subscriptions or on a free trial), and only premium subscribers can view them in online archives.", - }, - { - type: "free", - description: - "Free emails are sent out only to subscribers who are not paying for your newsletter (so you can send specific emails to convince them to pay, for instance!)", - }, - ]} -/> - - - | array", - description: - "If included, the response will be filtered down to emails whose `included_tags` include _all_ of the elements in the array. (This means that supplying two tags will only return emails which contain both tags, not either tag.)", - example: "", - optional: true, - }, - { - parameter: "excluded_tags", - type: "array | array", - description: - "If included, the response will be filtered down to emails whose `excluded_tags` include _all_ of the elements in the array. (This means that supplying two tags will only return emails which contain both tags, not either tag.)", - example: "", - optional: true, - }, - ]} -/> - - ---- - - - | array", - description: "", - optional: true, - }, - { - parameter: "included_tags", - type: "array | array", - description: "", - optional: true, - }, - { - parameter: "metadata", - type: "map", - description: "", - optional: true, - }, - ]} -/> - - ---- - -"} -/> - diff --git a/pages/api-reference/emails/metadata.json b/pages/api-reference/emails/metadata.json deleted file mode 100644 index 66a4594..0000000 --- a/pages/api-reference/emails/metadata.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": "Email", - "noun": "email", - "endpoint": "/v1/emails", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "body": "Lorem ipsum yadda yadda", - "subject": "This is my first email on Buttondown!", - "excluded_tags": [], - "included_tags": [], - "email_type": "public", - "metadata": {}, - "secondary_id": 3, - "external_url": "https://buttondown.email/jmduke/my-first-email" - } -} diff --git a/pages/api-reference/events-and-webhooks.mdx b/pages/api-reference/events-and-webhooks.mdx index 2a58065..0ac8b97 100644 --- a/pages/api-reference/events-and-webhooks.mdx +++ b/pages/api-reference/events-and-webhooks.mdx @@ -5,7 +5,7 @@ description: How to get a full lens of your Buttondown newsletter through struct import Layout from "../../components/Layout"; import Table from "../../components/Table"; -import OpenAPIEnums from "../../lib/enums.json"; +import OpenAPIEnums from "../../lib/openapi/enums.json"; export const meta = { title: "Events & webhooks", diff --git a/pages/api-reference/exports/index.mdx b/pages/api-reference/exports/index.mdx index 5ea84d6..b351a26 100644 --- a/pages/api-reference/exports/index.mdx +++ b/pages/api-reference/exports/index.mdx @@ -4,64 +4,18 @@ description: An API reference for the 'export' object in Buttondown --- import Layout from "../../../components/Layout"; -import ResponsesTable from "../../../components/api/ResponsesTable"; -import PathTable from "../../../components/api/PathTable"; import ObjectDescription from "../../../components/api/ObjectDescription"; -import ParametersTable from "../../../components/api/ParametersTable"; -import Endpoint from "../../../components/api/Endpoint"; -import DATA from "./metadata.json"; - -import { PAGE_PARAMETER } from "../../../lib/parameters"; -import OpenAPI from "../../../lib/openapi.json"; -import OpenAPIFixtures from "../../../lib/fixtures.json"; export const meta = { - title: DATA.title, + title: "Exports", }; -export const key = "ExportOutput"; -export const schema = OpenAPI.components.schemas[key]; -export const fixture = JSON.stringify(OpenAPIFixtures[key], null, 4); - export default ({ children }) => {children}; -export const foo = console.log(OpenAPIFixtures[key]) && 3; - # Exports -Some software applications may want programmatic access to their newsletter exports. This assists with a few niche -use cases, such as regular backups or data ingestion (into a data warehouse), or post-publishing processes that hinge -on email events. In general, you probably won't _need_ to use this endpoint unless you _absolutely_ need to use this endpoint. - -## The export object - -An export looks like this: - ({ - field: key, - type: schema.properties[key].type || schema.properties[key]["$ref"], - description: schema.properties[key].description, - }))} -/> - - - - - ---- - - - - - ---- - -"} + enums={["ExportCollection", "ExportStatus"]} + endpoints={["/exports", "/exports/{pk}"]} /> - diff --git a/pages/api-reference/exports/metadata.json b/pages/api-reference/exports/metadata.json deleted file mode 100644 index 585ee8b..0000000 --- a/pages/api-reference/exports/metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Export", - "noun": "export", - "endpoint": "/v1/exports" -} diff --git a/pages/api-reference/images/index.mdx b/pages/api-reference/images/index.mdx index 8504c0f..953f279 100644 --- a/pages/api-reference/images/index.mdx +++ b/pages/api-reference/images/index.mdx @@ -4,112 +4,18 @@ description: An API reference for the 'image' object in Buttondown --- import Layout from "../../../components/Layout"; -import ResponsesTable from "../../../components/api/ResponsesTable"; import ObjectDescription from "../../../components/api/ObjectDescription"; -import ParametersTable from "../../../components/api/ParametersTable"; -import Endpoint from "../../../components/api/Endpoint"; -import DATA from "./metadata.json"; -import { PAGE_PARAMETER } from '../../../lib/parameters'; export const meta = { - title: DATA.title, + title: "Images", }; -export const serializedImage = JSON.stringify(DATA.object, null, 4); - export default ({ children }) => {children}; # Images -Images are, well, images! Buttondown allows you to upload images to its secure [S3](https://aws.amazon.com/s3/) bucket and do with them what you will. This is sort of an odd duck of an API, to be sure, but if you want to be able to do things like draft and send emails completely on your iPad you need a surefire way of creating images. - -## The image object - -An image looks like this: - - - - - - ---- - - -", - description: "", - optional: false, - }, - ]} -/> - - ---- - -"} -/> - - ---- - -"} -/> - diff --git a/pages/api-reference/images/metadata.json b/pages/api-reference/images/metadata.json deleted file mode 100644 index dc43f66..0000000 --- a/pages/api-reference/images/metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "Image", - "noun": "image", - "endpoint": "/v1/images", - "object": { - "creation_date": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "image": "string" - } -} diff --git a/pages/api-reference/newsletters/index.mdx b/pages/api-reference/newsletters/index.mdx index 76d3530..e802eb8 100644 --- a/pages/api-reference/newsletters/index.mdx +++ b/pages/api-reference/newsletters/index.mdx @@ -4,149 +4,18 @@ description: An API reference for the 'newsletter' object in Buttondown --- import Layout from "../../../components/Layout"; -import ResponsesTable from "../../../components/api/ResponsesTable"; import ObjectDescription from "../../../components/api/ObjectDescription"; -import ParametersTable from "../../../components/api/ParametersTable"; -import ClosedBetaNotice from "../../../components/api/ClosedBetaNotice"; -import Endpoint from "../../../components/api/Endpoint"; -import DATA from "./metadata.json"; -import { PAGE_PARAMETER } from '../../../lib/parameters'; export const meta = { - title: DATA.title, + title: "Newsletters", }; export default ({ children }) => {children}; -# Newsletters - -## The newsletter object - -A newsletter looks like this: +# Newsletter - - - - - ---- - - - - - ---- - -"} - beta -/> - - ---- - -"} - beta -/> - - ---- - -"} - beta -/> - diff --git a/pages/api-reference/newsletters/metadata.json b/pages/api-reference/newsletters/metadata.json deleted file mode 100644 index 51c289e..0000000 --- a/pages/api-reference/newsletters/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title": "Newsletter", - "noun": "newsletter", - "endpoint": "/v1/newsletters", - "object": { - "api_key": "7f819f8f-8220-4dcd-b7e3-37c81ead8b7a", - "creation_date": "2019-08-24T14:15:22Z", - "description": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string", - "username": "string" - } -} diff --git a/pages/api-reference/subscribers/index.mdx b/pages/api-reference/subscribers/index.mdx index ab143c8..31e4a00 100644 --- a/pages/api-reference/subscribers/index.mdx +++ b/pages/api-reference/subscribers/index.mdx @@ -4,310 +4,23 @@ description: An API reference for the 'subscriber' object in Buttondown --- import Layout from "../../../components/Layout"; -import Table from "../../../components/Table"; -import Pill from "../../../components/Pill"; import ObjectDescription from "../../../components/api/ObjectDescription"; -import ParametersTable from "../../../components/api/ParametersTable"; -import PathTable from "../../../components/api/PathTable"; -import EnumTable from "../../../components/api/EnumTable"; -import ResponsesTable from "../../../components/api/ResponsesTable"; -import Endpoint from "../../../components/api/Endpoint"; -import ClosedBetaNotice from "../../../components/api/ClosedBetaNotice"; -import { MDXProvider } from "@mdx-js/react"; -import { H2, H1, P, H3, H4, Pre, Code } from "../../../components/Markdown"; -import { CheckCircleIcon } from "@heroicons/react/outline"; -import DATA from "./metadata.json"; -import { PAGE_PARAMETER } from "../../../lib/parameters"; -import OpenAPI from "../../../lib/openapi.json"; -import OpenAPIFixtures from "../../../lib/fixtures.json"; -import OpenAPIEnums from "../../../lib/enums.json"; -export const custom = () =>

; - -export const key = "SubscriberOutput"; -export const schema = OpenAPI.components.schemas[key]; -export const fixture = JSON.stringify(OpenAPIFixtures[key], null, 4); - -export default ({ children }) => {children}; export const meta = { title: "Subscribers", }; -# Subscribers - -Subscribers are the main way you collect email addresses and recipients on Buttondown. -They're what you see on your [subscribers page](https://buttondown.email/subscribers). - -## The subscriber object +export default ({ children }) => {children}; -A subscriber looks like this: +# Subscribers ({ - field: key, - type: schema.properties[key].type || schema.properties[key]["$ref"], - description: schema.properties[key].description, - }))} -/> - ---- - -## Listing subscribers - - - - - - - -#### Subscriber types - - - - - ---- - - - -If Buttondown cannot create a new subscriber with the email address you've provided, there are a few likely reasons why. They're enumerated below: - -- A subscriber with that email has already been unsubscribed. -- That email address (justin@gmail.com) is already subscribed. -- That email address (justin@gmail.com) is already subscribed, but has not confirmed their email. -- That email address (justin@gmail.com) is already subscribed, but has not provided payment. - - | array", - description: "", - optional: true, - }, - ]} -/> - - - ---- - - - - | array", - description: "", - optional: true, - }, - ]} -/> - - - ---- - - - - - ---- - - - - ---- - - - - - ---- - - - - diff --git a/pages/api-reference/subscribers/metadata.json b/pages/api-reference/subscribers/metadata.json deleted file mode 100644 index 03a6a41..0000000 --- a/pages/api-reference/subscribers/metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Subscriber", - "noun": "subscriber", - "endpoint": "/v1/subscribers" -} diff --git a/pages/api-reference/tags/index.mdx b/pages/api-reference/tags/index.mdx index de72238..3355ab8 100644 --- a/pages/api-reference/tags/index.mdx +++ b/pages/api-reference/tags/index.mdx @@ -4,120 +4,18 @@ description: An API reference for the 'tag' object in Buttondown --- import Layout from "../../../components/Layout"; -import ResponsesTable from "../../../components/api/ResponsesTable"; import ObjectDescription from "../../../components/api/ObjectDescription"; -import ParametersTable from "../../../components/api/ParametersTable"; -import Endpoint from "../../../components/api/Endpoint"; -import DATA from "./metadata.json"; -import {PAGE_PARAMETER} from '../../../lib/parameters'; export const meta = { - title: DATA.title, + title: "Tags", }; export default ({ children }) => {children}; # Tags -## The tag object - -A tag looks like this: - - - - - - ---- - - - - - ---- - -"} -/> - - ---- - -"} -/> - diff --git a/pages/api-reference/tags/metadata.json b/pages/api-reference/tags/metadata.json deleted file mode 100644 index 1f7797f..0000000 --- a/pages/api-reference/tags/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Tag", - "noun": "tag", - "endpoint": "/v1/tags", - "object": { - "color": "string", - "creation_date": "2019-08-24T14:15:22Z", - "description": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string" - } -} diff --git a/pages/getting-started/sending-your-first-email.mdx b/pages/getting-started/sending-your-first-email.mdx index 8420292..0e906aa 100644 --- a/pages/getting-started/sending-your-first-email.mdx +++ b/pages/getting-started/sending-your-first-email.mdx @@ -47,7 +47,7 @@ There are many ways to apply a tag to a subscriber, once you've created them: - Customizing your [subscription form](https://blog.buttondown.email/2019/07/17/customizing-your-subscription-form-to-tag-new-subscribers) - Manually tagging them -- Setting them via the [API](https://api.buttondown.email/v1/schema) +- Setting them via the [API](https://api.buttondown.email/v1/docs) ## Mail merges diff --git a/public/search-results.json b/public/search-results.json index de06026..7d89b73 100644 --- a/public/search-results.json +++ b/public/search-results.json @@ -79,7 +79,7 @@ }, { "path": "/api-reference/emails/", - "text": "Emails Emails are what you're for here on Buttondown at the end of the day, right? Creating an email via the API is just like creating one in the interface; it will instantly trigger sending actual emails, based on the tags and email type you provide. The email object An email looks like this: Email types Email types govern the visibility of an email, both in the sense of to whom it is originally delivered and whom can view it\nafter it's been sent out. By default, all emails are public. ", + "text": "Emails ", "title": "Emails", "description": "An API reference for the 'email' object in Buttondown" }, @@ -91,13 +91,13 @@ }, { "path": "/api-reference/exports/", - "text": "Exports Some software applications may want programmatic access to their newsletter exports. This assists with a few niche\nuse cases, such as regular backups or data ingestion (into a data warehouse), or post-publishing processes that hinge\non email events. In general, you probably won't need to use this endpoint unless you absolutely need to use this endpoint. The export object An export looks like this: ", + "text": "Exports ", "title": "Exports", "description": "An API reference for the 'export' object in Buttondown" }, { "path": "/api-reference/images/", - "text": "Images Images are, well, images! Buttondown allows you to upload images to its secure S3 bucket and do with them what you will. This is sort of an odd duck of an API, to be sure, but if you want to be able to do things like draft and send emails completely on your iPad you need a surefire way of creating images. The image object An image looks like this: ", + "text": "Images ", "title": "Images", "description": "An API reference for the 'image' object in Buttondown" }, @@ -109,19 +109,19 @@ }, { "path": "/api-reference/newsletters/", - "text": "Newsletters The newsletter object A newsletter looks like this: ", + "text": "Newsletter ", "title": "Newsletters", "description": "An API reference for the 'newsletter' object in Buttondown" }, { "path": "/api-reference/subscribers/", - "text": "Subscribers Subscribers are the main way you collect email addresses and recipients on Buttondown.\nThey're what you see on your subscribers page . The subscriber object A subscriber looks like this: Listing subscribers Subscriber types If Buttondown cannot create a new subscriber with the email address you've provided, there are a few likely reasons why. They're enumerated below: A subscriber with that email has already been unsubscribed. That email address ( justin@gmail.com ) is already subscribed. That email address ( justin@gmail.com ) is already subscribed, but has not confirmed their email. That email address ( justin@gmail.com ) is already subscribed, but has not provided payment. ", + "text": "Subscribers ", "title": "Subscribers", "description": "An API reference for the 'subscriber' object in Buttondown" }, { "path": "/api-reference/tags/", - "text": "Tags The tag object A tag looks like this: ", + "text": "Tags ", "title": "Tags", "description": "An API reference for the 'tag' object in Buttondown" }, From 0da8651e7e0587d2bff898e29adcb3d8e63a5ee8 Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 12:25:17 -0500 Subject: [PATCH 07/11] Fix builds --- components/api/EndpointDescription.tsx | 41 +++++++++----------- components/api/ObjectDescription.tsx | 19 +++++++-- components/api/PathTable.tsx | 49 ++++++++++++++++-------- lib/openapi/types.ts | 53 +++++++++++++++++++++++++- lib/openapi/utils.ts | 32 +++++++++++----- 5 files changed, 140 insertions(+), 54 deletions(-) diff --git a/components/api/EndpointDescription.tsx b/components/api/EndpointDescription.tsx index a6987be..6daaff4 100644 --- a/components/api/EndpointDescription.tsx +++ b/components/api/EndpointDescription.tsx @@ -5,21 +5,21 @@ import OpenAPI from "../../lib/openapi/openapi.json"; import { Method, Route, Operation } from "../../lib/openapi/types"; import { extractRefFromType, extractOperation } from "../../lib/openapi/utils"; -// type Path = keyof typeof OpenAPI.paths; -// type Operation = { -// summary: string; -// parameters: any[]; -// responses: any; -// requestBody?: { -// content: { -// "application/json": { -// schema: { -// $ref: string; -// }; -// }; -// }; -// }; -// }; +const extractRef = ( + operation: Operation> +): string | undefined => { + const body = operation.requestBody; + if (body === undefined) { + return undefined; + } + if ("application/json" in body.content) { + return body.content["application/json"].schema.$ref; + } + if ("multipart/form-data" in body.content) { + return body.content["multipart/form-data"].schema.$ref; + } + return undefined; +}; export default function EndpointDescription({ path, @@ -32,15 +32,8 @@ export default function EndpointDescription({ return ( <> {methods.map((method) => { - const operation = extractOperation(path, method) as any; - const body = operation.requestBody; - const parameters = body - ? ( - body.content["application/json"] || - body.content["multipart/form-data"] - ).schema.$ref - : undefined; - + const operation = extractOperation(path, method); + const parameters = extractRef(operation); const ref = parameters !== undefined ? extractRefFromType(parameters) : null; const schema = ref !== null ? OpenAPI.components.schemas[ref] : null; diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index bb602e2..fab63cd 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -6,7 +6,13 @@ import OpenAPI from "../../lib/openapi/openapi.json"; import OpenAPIEnums from "../../lib/openapi/enums.json"; import OpenAPIFixtures from "../../lib/openapi/fixtures.json"; import EndpointDescription from "./EndpointDescription"; -import { Enum, Object as OpenAPIObject, Route } from "../../lib/openapi/types"; +import { + Enum, + Object as OpenAPIObject, + Route, + ObjectDescription as OpenAPIObjectDescription, + Fixture, +} from "../../lib/openapi/types"; import MonospacedSpan from "../MonospacedSpan"; type Props = { @@ -21,8 +27,14 @@ type Field = { description: string; }; +const getFixtures = (name: keyof typeof OpenAPIFixtures): Fixture[] => { + const fixture = OpenAPIFixtures[name]; + return fixture; +}; + export default function ObjectDescription({ name, enums, endpoints }: Props) { - const schema = OpenAPI.components.schemas[name]; + const schema = OpenAPI.components.schemas[name] as OpenAPIObjectDescription; + // @ts-ignore const fields = Object.entries(schema.properties).map(([key, property]) => { return { field: key, @@ -32,7 +44,8 @@ export default function ObjectDescription({ name, enums, endpoints }: Props) { description: property.description, }; }); - const fixtures = OpenAPIFixtures[name]; + + const fixtures = getFixtures(name); return (
diff --git a/components/api/PathTable.tsx b/components/api/PathTable.tsx index dc3d0d9..2205740 100644 --- a/components/api/PathTable.tsx +++ b/components/api/PathTable.tsx @@ -2,12 +2,21 @@ import ResponsesTable from "./ResponsesTable"; import { extractBackingFixtureFromRef } from "../../lib/openapi/utils"; import { H3 } from "../Markdown"; import EnumTable from "./openapi/EnumTable"; +import { RequestBody } from "../../lib/openapi/types"; +import OpenAPI from "../../lib/openapi/openapi.json"; -const extractSchemaFromContent = (content: any): string | undefined => { +const extractSchemaFromContent = ( + content: RequestBody["content"] +): keyof typeof OpenAPI.components.schemas | undefined => { if (content) { - const schema = content["application/json"].schema; - if (schema) { - return schema["$ref"].split("/").pop(); + if ("application/json" in content) { + const schema = content["application/json"].schema; + if (schema) { + const potentialRef = schema["$ref"].split("/").pop(); + if (potentialRef) { + return potentialRef as keyof typeof OpenAPI.components.schemas; + } + } } } }; @@ -18,22 +27,32 @@ export default function PathTable({ content }: any) { description: content[key].description, fixture: extractSchemaFromContent(content[key].content), })); - const responseWithDistinctErrorType = responses.find((r) => - r.fixture?.startsWith("ErrorMessage_") - ); - if (responseWithDistinctErrorType) { + + const responseErrors = responses + .map((response) => + response.fixture !== undefined + ? extractBackingFixtureFromRef(response.fixture) + : undefined + ) + .map((fixture) => { + if (fixture === undefined) { + return undefined; + } + if (fixture.type === "ErrorMessage") { + return fixture.value; + } + return undefined; + }); + + const responseError = responseErrors.find((error) => error !== undefined); + + if (responseError) { return ( <>

Error codes

- + ); diff --git a/lib/openapi/types.ts b/lib/openapi/types.ts index d186123..b80b0be 100644 --- a/lib/openapi/types.ts +++ b/lib/openapi/types.ts @@ -6,16 +6,65 @@ type KeysOfType = keyof { [P in keyof T as V extends keyof T[P] ? P : never]: any; }; +// An OpenAPI spec has many `Routes` (e.g. /v1/subscribers). +// Every route has one or more `Methods` (e.g. GET, POST, PUT, DELETE). +// An `Operation` is a combination of a `Route` and a `Method`, with multiple characteristics: +// - `parameters` (e.g. query parameters, path parameters, etc.) +// - `requestBody` (e.g. the body of a POST request) +// - `responses` (e.g. the response body of a GET request) +// - `security` (e.g. the authentication requirements of a request) +// - `summary` (e.g. a short description of the operation) +// Some of those Operations may return `Objects`, which are top-level objects in the OpenAPI spec. +// Those Objects may have `Enums` as properties. export type Route = keyof typeof OpenAPI.paths; export type Method = keyof typeof OpenAPI.paths[R] & string; +export type Content = { + schema: { + $ref: string; + }; +}; +export type RequestBody = { + content: + | { + "application/json": Content; + } + | { + "multipart/form-data": Content; + }; +}; export type Operation< R extends Route, M extends Method -> = typeof OpenAPI.paths[R][M]; +> = typeof OpenAPI.paths[R][M] & { + operationId: string; + summary: string; + requestBody: RequestBody; + parameters: string[]; + responses: { + [key in string]: { + description: string; + content: { + "application/json": Content; + }; + }; + }; +}; export type Enum = keyof typeof OpenAPI.components.schemas & keyof typeof OpenAPIEnums; + export type Object = KeysOfType< typeof OpenAPI.components.schemas, "properties" > & - KeysOfType; + KeysOfType & + keyof typeof OpenAPIFixtures; + +export type ObjectDescription = Pick< + typeof OpenAPI["components"]["schemas"][Object], + "description" | "title" | "properties" +>; + +export type Fixture = { + description: string; + object: any; +}; diff --git a/lib/openapi/utils.ts b/lib/openapi/utils.ts index f8d5c9b..a418c04 100644 --- a/lib/openapi/utils.ts +++ b/lib/openapi/utils.ts @@ -1,4 +1,6 @@ import OpenAPI from "./openapi.json"; +import OpenAPIEnums from "./enums.json"; +import OpenAPIFixtures from "./fixtures.json"; import { Method, Operation, Route } from "./types"; export const extractRefFromType = ( @@ -13,19 +15,28 @@ export const extractRefFromType = ( return null; }; -export const extractBackingFixtureFromRef = ( - ref: string -): { - type?: "Page" | "ErrorMessage"; - value: string; -} => { +type BackingFixture = + | { + type: "ErrorMessage"; + value: keyof typeof OpenAPIEnums; + } + | { + type: "Page"; + value: keyof typeof OpenAPIFixtures; + } + | { + type: "Object"; + value: keyof typeof OpenAPIFixtures; + }; + +export const extractBackingFixtureFromRef = (ref: string): BackingFixture => { // Pages are wrapped like so: "Page_FOO_". // We want to extract 'FOO'. if (ref.startsWith("Page_")) { const pageName = ref.split("_")[1]; return { type: "Page", - value: pageName, + value: pageName as keyof typeof OpenAPIFixtures, }; } @@ -35,12 +46,13 @@ export const extractBackingFixtureFromRef = ( const errorMessageName = ref.split("_")[1]; return { type: "ErrorMessage", - value: errorMessageName, + value: errorMessageName as keyof typeof OpenAPIEnums, }; } return { - value: ref, + type: "Object", + value: ref as keyof typeof OpenAPIFixtures, }; }; @@ -48,7 +60,7 @@ export const extractOperation = ( route: R, method: Method ): Operation> => { - return OpenAPI.paths[route][method]; + return OpenAPI.paths[route][method] as Operation>; }; export const extractRouteInformation = >( From a735db9bcee0b10bf5e2e7f1dd961c9127d86d8b Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 13:42:16 -0500 Subject: [PATCH 08/11] Add parametes --- components/Table.tsx | 2 +- components/api/EndpointDescription.tsx | 70 ++++++---- components/api/ParametersTable.tsx | 7 +- lib/openapi/openapi.json | 171 +++++-------------------- lib/openapi/types.ts | 16 ++- 5 files changed, 98 insertions(+), 168 deletions(-) diff --git a/components/Table.tsx b/components/Table.tsx index b24ba19..e114861 100644 --- a/components/Table.tsx +++ b/components/Table.tsx @@ -8,7 +8,7 @@ export type Column = { }; export type Row = { - [key: string]: string | undefined; + [key: string]: string | boolean | undefined; }; type TableProps = { diff --git a/components/api/EndpointDescription.tsx b/components/api/EndpointDescription.tsx index 6daaff4..943386c 100644 --- a/components/api/EndpointDescription.tsx +++ b/components/api/EndpointDescription.tsx @@ -1,5 +1,5 @@ import Endpoint from "./Endpoint"; -import ParametersTable from "./ParametersTable"; +import ParametersTable, { Parameter } from "./ParametersTable"; import PathTable from "./PathTable"; import OpenAPI from "../../lib/openapi/openapi.json"; import { Method, Route, Operation } from "../../lib/openapi/types"; @@ -21,6 +21,49 @@ const extractRef = ( return undefined; }; +const extractParameters = ( + operation: Operation> +): Parameter[] => { + if (operation.parameters.length > 0) { + return operation.parameters + .filter((parameter) => parameter.in !== "path") + .map((parameter) => { + const type = + "type" in parameter.schema + ? parameter.schema.type + : extractRefFromType(parameter.schema.$ref); + return { + parameter: parameter.name, + type: type || "unknown", + description: "", + optional: !parameter.required, + }; + }); + } + const parameters = extractRef(operation); + const ref = parameters !== undefined ? extractRefFromType(parameters) : null; + const schema = ref !== null ? OpenAPI.components.schemas[ref] : null; + if (schema === null) { + return []; + } + if ("properties" in schema) { + return Object.keys(schema.properties).map((parameter: any) => { + const qualifiedParameter = (schema.properties as any)[parameter] as { + type: string; + description: string; + }; + return { + parameter, + type: qualifiedParameter.type, + description: qualifiedParameter.description, + optional: + "required" in schema ? !schema.required.includes(parameter) : true, + }; + }); + } + return []; +}; + export default function EndpointDescription({ path, }: { @@ -33,32 +76,11 @@ export default function EndpointDescription({ <> {methods.map((method) => { const operation = extractOperation(path, method); - const parameters = extractRef(operation); - const ref = - parameters !== undefined ? extractRefFromType(parameters) : null; - const schema = ref !== null ? OpenAPI.components.schemas[ref] : null; + const parameters = extractParameters(operation); return (
- {parameters && schema && "properties" in schema && ( - { - const qualifiedParameter = (schema.properties as any)[ - parameter - ] as { - type: string; - description: string; - }; - return { - parameter, - type: qualifiedParameter.type, - description: qualifiedParameter.description, - }; - } - )} - /> - )} + {parameters.length > 0 && }
); diff --git a/components/api/ParametersTable.tsx b/components/api/ParametersTable.tsx index 1ec90bb..58954b8 100644 --- a/components/api/ParametersTable.tsx +++ b/components/api/ParametersTable.tsx @@ -13,7 +13,7 @@ function RawHTML(s: string) { return
; } -function CheckMark(s: string) { +function CheckMark(s: boolean) { return ( s && ( CheckMark(c.optional), }, ]} content={content.map((row) => ({ diff --git a/lib/openapi/openapi.json b/lib/openapi/openapi.json index c3ff0a7..2ac5e1e 100644 --- a/lib/openapi/openapi.json +++ b/lib/openapi/openapi.json @@ -386,9 +386,7 @@ "format": "binary" } }, - "required": [ - "image" - ] + "required": ["image"] } } }, @@ -489,11 +487,7 @@ "in": "query", "name": "status", "schema": { - "default": [ - "about_to_send", - "in_flight", - "sent" - ], + "default": ["about_to_send", "in_flight", "sent"], "type": "array", "items": { "$ref": "#/components/schemas/EmailStatus" @@ -1302,12 +1296,7 @@ "ExportStatus": { "title": "Status", "description": "\n Represents the status of an export.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", - "enum": [ - "error", - "in_progress", - "not_started", - "ready" - ], + "enum": ["error", "in_progress", "not_started", "ready"], "type": "string" }, "Export": { @@ -1338,11 +1327,7 @@ "format": "date-time" } }, - "required": [ - "id", - "creation_date", - "status" - ] + "required": ["id", "creation_date", "status"] }, "ErrorMessage": { "title": "ErrorMessage", @@ -1364,9 +1349,7 @@ } } }, - "required": [ - "detail" - ] + "required": ["detail"] }, "ExportCollection": { "title": "Collection", @@ -1393,9 +1376,7 @@ } } }, - "required": [ - "collections" - ] + "required": ["collections"] }, "Page_Export_": { "title": "Page[Export]", @@ -1421,10 +1402,7 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "Tag": { "title": "Tag", @@ -1458,13 +1436,7 @@ "format": "date-time" } }, - "required": [ - "id", - "name", - "color", - "secondary_id", - "creation_date" - ] + "required": ["id", "name", "color", "secondary_id", "creation_date"] }, "TagInput": { "title": "TagInput", @@ -1483,10 +1455,7 @@ "type": "string" } }, - "required": [ - "name", - "color" - ] + "required": ["name", "color"] }, "Page_Tag_": { "title": "Page[Tag]", @@ -1512,17 +1481,12 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "UpdateTagErrorCode": { "title": "UpdateTagErrorCode", "description": "\n A potential error code that can be returned when updating a tag.\n ", - "enum": [ - "name_already_exists" - ], + "enum": ["name_already_exists"], "type": "string" }, "ErrorMessage_UpdateTagErrorCode_": { @@ -1545,10 +1509,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "TagUpdateInput": { "title": "TagUpdateInput", @@ -1588,23 +1549,12 @@ "type": "string" } }, - "required": [ - "id", - "creation_date", - "image" - ] + "required": ["id", "creation_date", "image"] }, "EmailType": { "title": "Type", "description": "\n Represents the audience of an email, and to whom it is visible both in the initial\n email and in online archives.\n ", - "enum": [ - "public", - "private", - "premium", - "free", - "archival", - "hidden" - ], + "enum": ["public", "private", "premium", "free", "archival", "hidden"], "type": "string" }, "EmailStatus": { @@ -1701,11 +1651,7 @@ "EmailCreationErrorCode": { "title": "EmailCreationErrorCode", "description": "\n Represents the type of error that occurred when creating an email.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", - "enum": [ - "subject_invalid", - "email_duplicate", - "email_invalid" - ], + "enum": ["subject_invalid", "email_duplicate", "email_invalid"], "type": "string" }, "ErrorMessage_EmailCreationErrorCode_": { @@ -1728,10 +1674,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "EmailInput": { "title": "EmailInput", @@ -1808,9 +1751,7 @@ } } }, - "required": [ - "subject" - ] + "required": ["subject"] }, "Page_Email_": { "title": "Page[Email]", @@ -1836,10 +1777,7 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "Analytics": { "title": "Analytics", @@ -1912,13 +1850,7 @@ "SubscriberSource": { "title": "Source", "description": "\n Represents the original provenance of a subscriber. This value is not exposed\n to subscribers; it's only used for internal tracking purposes and governs some\n of the behavior of the subscriber (i.e. whether or not to require double\n opt-in.)\n ", - "enum": [ - "api", - "import", - "organic", - "user", - "admin" - ], + "enum": ["api", "import", "organic", "user", "admin"], "type": "string" }, "Subscriber": { @@ -2030,9 +1962,7 @@ "type": "string" } }, - "required": [ - "email" - ] + "required": ["email"] }, "Page_Subscriber_": { "title": "Page[Subscriber]", @@ -2058,17 +1988,12 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "ListSubscribersErrorCode": { "title": "ListSubscribersErrorCode", "description": "\n Represents the type of error that occurred when listing subscribers.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", - "enum": [ - "invalid_tag" - ], + "enum": ["invalid_tag"], "type": "string" }, "ErrorMessage_ListSubscribersErrorCode_": { @@ -2091,10 +2016,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "UpdateSubscriberErrorCode": { "title": "UpdateSubscriberErrorCode", @@ -2126,10 +2048,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "SubscriberUpdateInput": { "title": "SubscriberUpdateInput", @@ -2230,17 +2149,12 @@ "type": "integer" } }, - "required": [ - "results", - "count" - ] + "required": ["results", "count"] }, "CreateNewsletterErrorCode": { "title": "CreateNewsletterErrorCode", "description": "\n Represents the type of error that occurred when creating a newsletter.\n\n Human-readable error messages are provided in the `detail` field of the response;\n these values are meant to be parseable by code or client logic.\n ", - "enum": [ - "username_already_exists" - ], + "enum": ["username_already_exists"], "type": "string" }, "ErrorMessage_CreateNewsletterErrorCode_": { @@ -2263,10 +2177,7 @@ } } }, - "required": [ - "code", - "detail" - ] + "required": ["code", "detail"] }, "NewsletterInput": { "title": "NewsletterInput", @@ -2285,11 +2196,7 @@ "type": "string" } }, - "required": [ - "username", - "name", - "description" - ] + "required": ["username", "name", "description"] }, "NewsletterUpdateInput": { "title": "NewsletterUpdateInput", @@ -2312,12 +2219,7 @@ "BulkActionStatus": { "title": "Status", "description": "\n Represents the status of a bulk action.\n\n No action is required to move from one state or another; Buttondown\n internally handles the transitions, and exposing the status is for\n observability purposes only.\n ", - "enum": [ - "not_started", - "in_progress", - "processed", - "failed" - ], + "enum": ["not_started", "in_progress", "processed", "failed"], "type": "string" }, "BulkActionType": { @@ -2387,13 +2289,7 @@ } } }, - "required": [ - "id", - "creation_date", - "status", - "type", - "metadata" - ] + "required": ["id", "creation_date", "status", "type", "metadata"] }, "BulkActionInput": { "title": "BulkActionInput", @@ -2423,10 +2319,7 @@ } } }, - "required": [ - "type", - "metadata" - ] + "required": ["type", "metadata"] } }, "securitySchemes": { @@ -2436,4 +2329,4 @@ } } } -} \ No newline at end of file +} diff --git a/lib/openapi/types.ts b/lib/openapi/types.ts index b80b0be..46ce501 100644 --- a/lib/openapi/types.ts +++ b/lib/openapi/types.ts @@ -32,6 +32,20 @@ export type RequestBody = { "multipart/form-data": Content; }; }; +export type Parameter = { + in: "path" | "query"; + name: string; + schema: + | { + title: string; + type: string; + description?: string; + } + | { + $ref: string; + }; + required: boolean; +}; export type Operation< R extends Route, M extends Method @@ -39,7 +53,7 @@ export type Operation< operationId: string; summary: string; requestBody: RequestBody; - parameters: string[]; + parameters: Parameter[]; responses: { [key in string]: { description: string; From f7bdffc5b6c9be3fe122e8af474e88518e8cdac8 Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 13:55:12 -0500 Subject: [PATCH 09/11] Add initial spec from ninja --- components/api/EnumTable.tsx | 34 --------------------------- components/api/ObjectDescription.tsx | 4 ++-- components/api/ParametersTable.tsx | 5 +--- components/api/openapi/EnumTable.tsx | 35 ++++++++++++++++++++++++---- lib/openapi/types.ts | 7 +++++- 5 files changed, 39 insertions(+), 46 deletions(-) delete mode 100644 components/api/EnumTable.tsx diff --git a/components/api/EnumTable.tsx b/components/api/EnumTable.tsx deleted file mode 100644 index 6112efa..0000000 --- a/components/api/EnumTable.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import Table from "../Table"; -import Pill, { Variant } from "../Pill"; - -type Row = { - type: string; - name?: string; - variant: Variant; -}; - -export default function EnumTable({ e }: any) { - return ( -

( - {row.name || row.type} - ), - }, - { - title: "identifier", - component: (row: Row) => ( - {row.type} - ), - }, - { title: "description" }, - ]} - content={Object.keys(e).map((key) => ({ - type: key, - ...e[key], - }))} - /> - ); -} diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index fab63cd..7f7302c 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -1,7 +1,7 @@ import { Code, H4, P } from "../Markdown"; import Table from "../Table"; import { extractRefFromType } from "../../lib/openapi/utils"; -import EnumTable from "./EnumTable"; +import EnumTable from "./openapi/EnumTable"; import OpenAPI from "../../lib/openapi/openapi.json"; import OpenAPIEnums from "../../lib/openapi/enums.json"; import OpenAPIFixtures from "../../lib/openapi/fixtures.json"; @@ -97,7 +97,7 @@ export default function ObjectDescription({ name, enums, endpoints }: Props) {

{OpenAPI.components.schemas[e].description}


- + ); })} diff --git a/components/api/ParametersTable.tsx b/components/api/ParametersTable.tsx index 58954b8..5f1670b 100644 --- a/components/api/ParametersTable.tsx +++ b/components/api/ParametersTable.tsx @@ -4,10 +4,7 @@ import { H3 } from "../Markdown"; import Table, { Row } from "../Table"; import remark from "remark"; import remarkHtml from "remark-html"; - -function MonospacedSpan(s: string) { - return {s}; -} +import MonospacedSpan from "../MonospacedSpan"; function RawHTML(s: string) { return
; diff --git a/components/api/openapi/EnumTable.tsx b/components/api/openapi/EnumTable.tsx index b852d34..f34aa6d 100644 --- a/components/api/openapi/EnumTable.tsx +++ b/components/api/openapi/EnumTable.tsx @@ -1,11 +1,36 @@ import OpenAPIEnums from "../../../lib/openapi/enums.json"; -import OldEnumTable from "../EnumTable"; +import { EnumDescription } from "../../../lib/openapi/types"; +import Table, { Row } from "../../Table"; +import Pill, { Variant } from "../../Pill"; -type props = { +type Props = { enum: keyof typeof OpenAPIEnums; }; -export default function EnumTable({ enum: enumName }: props) { - const enumValues = OpenAPIEnums[enumName]; - return ; +export default function EnumTable({ enum: enumName }: Props) { + const valueToSpec = OpenAPIEnums[enumName]; + const rows = Object.entries(valueToSpec).map(([key, value]) => ({ + type: key, + ...value, + })); + return ( +
( + {row.name || row.type} + ), + }, + { + title: "identifier", + component: (row: Row) => ( + {row.type} + ), + }, + { title: "description" }, + ]} + content={rows} + /> + ); } diff --git a/lib/openapi/types.ts b/lib/openapi/types.ts index 46ce501..c895afd 100644 --- a/lib/openapi/types.ts +++ b/lib/openapi/types.ts @@ -1,6 +1,7 @@ import OpenAPI from "./openapi.json"; import OpenAPIEnums from "./enums.json"; import OpenAPIFixtures from "./fixtures.json"; +import { Variant } from "../../components/Pill"; type KeysOfType = keyof { [P in keyof T as V extends keyof T[P] ? P : never]: any; @@ -65,7 +66,11 @@ export type Operation< }; export type Enum = keyof typeof OpenAPI.components.schemas & keyof typeof OpenAPIEnums; - +export type EnumDescription = { + description: string; + name: string; + variant: Variant; +}; export type Object = KeysOfType< typeof OpenAPI.components.schemas, "properties" From 35b801d8381309bd93eddd4eb010fa5cf24496b9 Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 13:55:36 -0500 Subject: [PATCH 10/11] Add initial spec from ninja --- components/api/ObjectDescription.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index 7f7302c..424316a 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -3,7 +3,6 @@ import Table from "../Table"; import { extractRefFromType } from "../../lib/openapi/utils"; import EnumTable from "./openapi/EnumTable"; import OpenAPI from "../../lib/openapi/openapi.json"; -import OpenAPIEnums from "../../lib/openapi/enums.json"; import OpenAPIFixtures from "../../lib/openapi/fixtures.json"; import EndpointDescription from "./EndpointDescription"; import { From 10ff845a82776a629c9350e117817628864c366e Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 12 Dec 2022 14:40:52 -0500 Subject: [PATCH 11/11] Add parametes --- components/Layout.tsx | 108 ++++------------ components/MarkdownString.tsx | 10 ++ components/Scaffolding.tsx | 67 ++++++++++ components/TableOfContents.tsx | 6 +- components/api/Endpoint.tsx | 2 +- components/api/ObjectDescription.tsx | 146 +++++++++++++--------- components/api/ParametersTable.tsx | 15 +-- components/api/openapi/EnumTable.tsx | 1 - package-lock.json | 14 +-- package.json | 2 +- pages/api-reference/bulk-actions.mdx | 21 ---- pages/api-reference/bulk-actions.tsx | 14 +++ pages/api-reference/emails.tsx | 14 +++ pages/api-reference/emails/index.mdx | 21 ---- pages/api-reference/exports.tsx | 14 +++ pages/api-reference/exports/index.mdx | 21 ---- pages/api-reference/images.tsx | 14 +++ pages/api-reference/images/index.mdx | 21 ---- pages/api-reference/newsletters.tsx | 14 +++ pages/api-reference/newsletters/index.mdx | 21 ---- pages/api-reference/subscribers.tsx | 19 +++ pages/api-reference/subscribers/index.mdx | 26 ---- pages/api-reference/tags.tsx | 14 +++ pages/api-reference/tags/index.mdx | 21 ---- public/search-results.json | 42 ------- 25 files changed, 308 insertions(+), 360 deletions(-) create mode 100644 components/MarkdownString.tsx create mode 100644 components/Scaffolding.tsx delete mode 100644 pages/api-reference/bulk-actions.mdx create mode 100644 pages/api-reference/bulk-actions.tsx create mode 100644 pages/api-reference/emails.tsx delete mode 100644 pages/api-reference/emails/index.mdx create mode 100644 pages/api-reference/exports.tsx delete mode 100644 pages/api-reference/exports/index.mdx create mode 100644 pages/api-reference/images.tsx delete mode 100644 pages/api-reference/images/index.mdx create mode 100644 pages/api-reference/newsletters.tsx delete mode 100644 pages/api-reference/newsletters/index.mdx create mode 100644 pages/api-reference/subscribers.tsx delete mode 100644 pages/api-reference/subscribers/index.mdx create mode 100644 pages/api-reference/tags.tsx delete mode 100644 pages/api-reference/tags/index.mdx diff --git a/components/Layout.tsx b/components/Layout.tsx index c426737..cdafb77 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -1,12 +1,7 @@ import { MDXProvider } from "@mdx-js/react"; -import Head from "next/head"; -import { useState } from "react"; import React from "react"; -import { GlobalHotKeys } from "react-hotkeys"; import slugify from "../lib/slugify"; -import Footer from "./Footer"; -import Header from "./Header"; import { A, Blockquote, @@ -23,12 +18,8 @@ import { Pre, Ul, } from "./Markdown"; -import Sidebar from "./Sidebar/Sidebar"; import TableOfContents from "./TableOfContents"; - -const keyMap = { - TRIGGER_SEARCH: "/", -}; +import Scaffolding from "./Scaffolding"; const extractAnchorFromHeader = (child: React.ReactElement) => ({ url: "#" + slugify(child.props.children), @@ -46,14 +37,6 @@ const extractAnchorFromEndpoint = (child: React.ReactElement) => ({ }); export default function Layout({ meta, children }: any) { - const [searchOpen, setSearchOpen] = useState(false); - const [sidebarOpen, setSidebarOpen] = useState(false); - - const title = - meta && meta.title - ? `${meta.title} • Buttondown documentation` - : "Buttondown documentation"; - const anchors = React.Children.toArray(children) .filter( (child: any) => @@ -67,71 +50,30 @@ export default function Layout({ meta, children }: any) { }); return ( - <> - { - e?.preventDefault(); - setSearchOpen(true); - }, - }} - /> -
- - {title} - - {meta && meta.description && ( - - )} - - - - - -
-
-
-
-
- - {children} - -
- -
-
-
-
+ +
+ + {children} +
- + +
); } diff --git a/components/MarkdownString.tsx b/components/MarkdownString.tsx new file mode 100644 index 0000000..5597255 --- /dev/null +++ b/components/MarkdownString.tsx @@ -0,0 +1,10 @@ +import remark from "remark"; +import remarkHtml from "remark-html"; + +const render = (markdown: string): string => { + return remark().use(remarkHtml).processSync(markdown).toString(); +}; + +export default function MarkdownString({ text }: { text: string }) { + return
; +} diff --git a/components/Scaffolding.tsx b/components/Scaffolding.tsx new file mode 100644 index 0000000..a2bbceb --- /dev/null +++ b/components/Scaffolding.tsx @@ -0,0 +1,67 @@ +import Head from "next/head"; +import { useState } from "react"; +import React from "react"; +import { GlobalHotKeys } from "react-hotkeys"; + +import Footer from "./Footer"; +import Header from "./Header"; +import Sidebar from "./Sidebar/Sidebar"; + +const keyMap = { + TRIGGER_SEARCH: "/", +}; + +export default function Layout({ meta, children }: any) { + const [searchOpen, setSearchOpen] = useState(false); + const [sidebarOpen, setSidebarOpen] = useState(false); + + const title = + meta && meta.title + ? `${meta.title} • Buttondown documentation` + : "Buttondown documentation"; + + return ( + <> + { + e?.preventDefault(); + setSearchOpen(true); + }, + }} + /> +
+ + {title} + + {meta && meta.description && ( + + )} + + + + + +
+
+
+
{children}
+
+
+
+
+ + ); +} diff --git a/components/TableOfContents.tsx b/components/TableOfContents.tsx index aab8a6f..017dcbf 100644 --- a/components/TableOfContents.tsx +++ b/components/TableOfContents.tsx @@ -1,7 +1,7 @@ type Anchor = { depth: Number; text: string; - url: string; + url?: string; }; type Props = { @@ -18,12 +18,12 @@ export default function TableOfContents({ anchors }: Props) {
)} {anchors.map((anchor, i) => { - return anchor.text === "FAQs" ? ( + return anchor.text === "FAQs" || anchor.url === undefined ? (
- FAQs + {anchor.text}
) : (
({ }: Props) { return ( <> -

{title}

+

{title}

{beta && ( <> diff --git a/components/api/ObjectDescription.tsx b/components/api/ObjectDescription.tsx index 424316a..e228f74 100644 --- a/components/api/ObjectDescription.tsx +++ b/components/api/ObjectDescription.tsx @@ -1,10 +1,11 @@ -import { Code, H4, P } from "../Markdown"; +import { Code, H1, H4, P } from "../Markdown"; import Table from "../Table"; import { extractRefFromType } from "../../lib/openapi/utils"; import EnumTable from "./openapi/EnumTable"; import OpenAPI from "../../lib/openapi/openapi.json"; import OpenAPIFixtures from "../../lib/openapi/fixtures.json"; import EndpointDescription from "./EndpointDescription"; +import MarkdownString from "../MarkdownString"; import { Enum, Object as OpenAPIObject, @@ -13,6 +14,7 @@ import { Fixture, } from "../../lib/openapi/types"; import MonospacedSpan from "../MonospacedSpan"; +import TableOfContents from "../TableOfContents"; type Props = { name: OpenAPIObject; @@ -47,67 +49,95 @@ export default function ObjectDescription({ name, enums, endpoints }: Props) { const fixtures = getFixtures(name); return ( -
-
-

{schema.description}

- {fixtures.length > 0 && - fixtures.map((fixture) => { + <> +
+
+
+

{schema.title}

+ + {fixtures.length > 0 && + fixtures.map((fixture) => { + return ( + <> +

{fixture.description}

+ + {JSON.stringify(fixture.object, null, 4)} + + + ); + })} +
+ +
MonospacedSpan(c.field), + }, + { + title: "type", + component: (c: Field) => + MonospacedSpan( + extractRefFromType(c.type) ? ( + + {extractRefFromType(c.type)} + + ) : ( + c.type + ) + ), + }, + { title: "description" }, + ]} + content={fields} + /> + + {enums.map((e) => { return ( - <> -

{fixture.description}

- - {JSON.stringify(fixture.object, null, 4)} - - +
+ +

+ {OpenAPI.components.schemas[e].title} ({MonospacedSpan(e)}) +

+

{OpenAPI.components.schemas[e].description}

+
+ +
); })} - -
MonospacedSpan(c.field), - }, - { - title: "type", - component: (c: Field) => - MonospacedSpan( - extractRefFromType(c.type) ? ( - - {extractRefFromType(c.type)} - - ) : ( - c.type - ) - ), - }, - { title: "description" }, - ]} - content={fields} + {endpoints.map((e) => { + return ( +
+ +
+ ); + })} + + + 0 ? { text: "Enums", depth: 0 } : null, + ...enums.map((e) => { + return { + text: e, + depth: 1, + url: `#${e}`, + }; + }), + endpoints.length > 0 ? { text: "Endpoints", depth: 0 } : null, + ...endpoints.map((e) => { + return { + text: e, + depth: 1, + url: `#${e}`, + }; + }), + ] + .filter((s) => s !== null) + .map((s) => s as { text: string; depth: number; url?: string })} /> - - {enums.map((e) => { - return ( -
- -

- {OpenAPI.components.schemas[e].title} ({MonospacedSpan(e)}) -

-

{OpenAPI.components.schemas[e].description}

-
- -
- ); - })} - - {endpoints.map((e) => { - return ( -
- -
- ); - })} - + ); } diff --git a/components/api/ParametersTable.tsx b/components/api/ParametersTable.tsx index 5f1670b..7e06a85 100644 --- a/components/api/ParametersTable.tsx +++ b/components/api/ParametersTable.tsx @@ -2,13 +2,8 @@ import { CheckCircleIcon } from "@heroicons/react/outline"; import { H3 } from "../Markdown"; import Table, { Row } from "../Table"; -import remark from "remark"; -import remarkHtml from "remark-html"; import MonospacedSpan from "../MonospacedSpan"; - -function RawHTML(s: string) { - return
; -} +import MarkdownString from "../MarkdownString"; function CheckMark(s: boolean) { return ( @@ -34,10 +29,6 @@ type Props = { content: Array; }; -const renderMarkdown = (markdown: string): string => { - return remark().use(remarkHtml).processSync(markdown).toString(); -}; - export default function ParametersTable({ content }: Props) { return ( <> @@ -54,7 +45,8 @@ export default function ParametersTable({ content }: Props) { }, { title: "description", - component: (c: Parameter) => RawHTML(c.description), + component: (c: Parameter) => + MarkdownString({ text: c.description }), }, { title: "optional", @@ -64,7 +56,6 @@ export default function ParametersTable({ content }: Props) { ]} content={content.map((row) => ({ ...row, - description: renderMarkdown(row.description), }))} /> diff --git a/components/api/openapi/EnumTable.tsx b/components/api/openapi/EnumTable.tsx index f34aa6d..fe07225 100644 --- a/components/api/openapi/EnumTable.tsx +++ b/components/api/openapi/EnumTable.tsx @@ -1,5 +1,4 @@ import OpenAPIEnums from "../../../lib/openapi/enums.json"; -import { EnumDescription } from "../../../lib/openapi/types"; import Table, { Row } from "../../Table"; import Pill, { Variant } from "../../Pill"; diff --git a/package-lock.json b/package-lock.json index 2c38fb4..a228079 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "remark-html": "^15.0.1", "remark-mdx": "^1.6.22", "tailwindcss": "^3.0.8", - "typescript": "^4.3.5" + "typescript": "^4.9.4" } }, "node_modules/@babel/code-frame": { @@ -22150,9 +22150,9 @@ } }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -41165,9 +41165,9 @@ } }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true }, "ua-parser-js": { diff --git a/package.json b/package.json index 4f57246..3cd3652 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,6 @@ "remark-html": "^15.0.1", "remark-mdx": "^1.6.22", "tailwindcss": "^3.0.8", - "typescript": "^4.3.5" + "typescript": "^4.9.4" } } diff --git a/pages/api-reference/bulk-actions.mdx b/pages/api-reference/bulk-actions.mdx deleted file mode 100644 index 25291cd..0000000 --- a/pages/api-reference/bulk-actions.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Bulk actions -description: An API reference for the 'bulk action' object in Buttondown ---- - -import Layout from "../../components/Layout"; -import ObjectDescription from "../../components/api/ObjectDescription"; - -export const meta = { - title: "Bulk actions", -}; - -export default ({ children }) => {children}; - -# Bulk actions - - diff --git a/pages/api-reference/bulk-actions.tsx b/pages/api-reference/bulk-actions.tsx new file mode 100644 index 0000000..54ae946 --- /dev/null +++ b/pages/api-reference/bulk-actions.tsx @@ -0,0 +1,14 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function BulkActions() { + return ( + + + + ); +} diff --git a/pages/api-reference/emails.tsx b/pages/api-reference/emails.tsx new file mode 100644 index 0000000..86e9512 --- /dev/null +++ b/pages/api-reference/emails.tsx @@ -0,0 +1,14 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function Emails() { + return ( + + + + ); +} diff --git a/pages/api-reference/emails/index.mdx b/pages/api-reference/emails/index.mdx deleted file mode 100644 index 21891f5..0000000 --- a/pages/api-reference/emails/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Emails -description: An API reference for the 'email' object in Buttondown ---- - -import Layout from "../../../components/Layout"; -import ObjectDescription from "../../../components/api/ObjectDescription"; - -export const meta = { - title: "Emails", -}; - -export default ({ children }) => {children}; - -# Emails - - diff --git a/pages/api-reference/exports.tsx b/pages/api-reference/exports.tsx new file mode 100644 index 0000000..77ca82d --- /dev/null +++ b/pages/api-reference/exports.tsx @@ -0,0 +1,14 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function Exports() { + return ( + + + + ); +} diff --git a/pages/api-reference/exports/index.mdx b/pages/api-reference/exports/index.mdx deleted file mode 100644 index b351a26..0000000 --- a/pages/api-reference/exports/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Exports -description: An API reference for the 'export' object in Buttondown ---- - -import Layout from "../../../components/Layout"; -import ObjectDescription from "../../../components/api/ObjectDescription"; - -export const meta = { - title: "Exports", -}; - -export default ({ children }) => {children}; - -# Exports - - diff --git a/pages/api-reference/images.tsx b/pages/api-reference/images.tsx new file mode 100644 index 0000000..c02e198 --- /dev/null +++ b/pages/api-reference/images.tsx @@ -0,0 +1,14 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function Images() { + return ( + + + + ); +} diff --git a/pages/api-reference/images/index.mdx b/pages/api-reference/images/index.mdx deleted file mode 100644 index 953f279..0000000 --- a/pages/api-reference/images/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Images -description: An API reference for the 'image' object in Buttondown ---- - -import Layout from "../../../components/Layout"; -import ObjectDescription from "../../../components/api/ObjectDescription"; - -export const meta = { - title: "Images", -}; - -export default ({ children }) => {children}; - -# Images - - diff --git a/pages/api-reference/newsletters.tsx b/pages/api-reference/newsletters.tsx new file mode 100644 index 0000000..ffdf7f3 --- /dev/null +++ b/pages/api-reference/newsletters.tsx @@ -0,0 +1,14 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function Newsletters() { + return ( + + + + ); +} diff --git a/pages/api-reference/newsletters/index.mdx b/pages/api-reference/newsletters/index.mdx deleted file mode 100644 index e802eb8..0000000 --- a/pages/api-reference/newsletters/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Newsletters -description: An API reference for the 'newsletter' object in Buttondown ---- - -import Layout from "../../../components/Layout"; -import ObjectDescription from "../../../components/api/ObjectDescription"; - -export const meta = { - title: "Newsletters", -}; - -export default ({ children }) => {children}; - -# Newsletter - - diff --git a/pages/api-reference/subscribers.tsx b/pages/api-reference/subscribers.tsx new file mode 100644 index 0000000..8e80a88 --- /dev/null +++ b/pages/api-reference/subscribers.tsx @@ -0,0 +1,19 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function Subscribers() { + return ( + + + + ); +} diff --git a/pages/api-reference/subscribers/index.mdx b/pages/api-reference/subscribers/index.mdx deleted file mode 100644 index 31e4a00..0000000 --- a/pages/api-reference/subscribers/index.mdx +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Subscribers -description: An API reference for the 'subscriber' object in Buttondown ---- - -import Layout from "../../../components/Layout"; -import ObjectDescription from "../../../components/api/ObjectDescription"; - -export const meta = { - title: "Subscribers", -}; - -export default ({ children }) => {children}; - -# Subscribers - - diff --git a/pages/api-reference/tags.tsx b/pages/api-reference/tags.tsx new file mode 100644 index 0000000..4a7f666 --- /dev/null +++ b/pages/api-reference/tags.tsx @@ -0,0 +1,14 @@ +import ObjectDescription from "../../components/api/ObjectDescription"; +import Scaffolding from "../../components/Scaffolding"; + +export default function Tags() { + return ( + + + + ); +} diff --git a/pages/api-reference/tags/index.mdx b/pages/api-reference/tags/index.mdx deleted file mode 100644 index 3355ab8..0000000 --- a/pages/api-reference/tags/index.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Tags -description: An API reference for the 'tag' object in Buttondown ---- - -import Layout from "../../../components/Layout"; -import ObjectDescription from "../../../components/api/ObjectDescription"; - -export const meta = { - title: "Tags", -}; - -export default ({ children }) => {children}; - -# Tags - - diff --git a/public/search-results.json b/public/search-results.json index 7d89b73..2bbe21e 100644 --- a/public/search-results.json +++ b/public/search-results.json @@ -65,66 +65,24 @@ "title": "Authentication", "description": "How to authenticate API requests to Buttondown's servers" }, - { - "path": "/api-reference/bulk-actions", - "text": "Bulk actions ", - "title": "Bulk actions", - "description": "An API reference for the 'bulk action' object in Buttondown" - }, { "path": "/api-reference/changelog", "text": "Changelog The changelog provides a list of dated updates, each of which contains a number of potentially backwards-incompatible changes. There is no explicit versioning in the API at this time; all changes will be either compatible or breaking. (If I attempt any brittle changes to the API that may break current implementations, I'll be sure to add version gates.) 2022-11-24 Added , which returns aggregated information about opens, clicks, and other events for a given email that you've sent. 2022-11-19 Removed the , , and endpoints. The behavior of all three endpoints have been subsumed into and : you can create (or list) drafts by specifying you can create (or list) scheduled emails by specifying you can list unsubscribers by specifying 2022-03-08 Added support for filtering emails in on and . 2021-11-26 Took the ability to send specific emails to subscribers out of beta; added , which allows you to send\nreminders to your subscribers to confirm their email address. 2021-08-27 Added support for Exports via the endpoint. 2021-01-02 Added support to set and retrieve metadata on Emails. 2020-12-23 Added deletion and update abilities to the Scheduled emails endpoint, giving you much more programmability than the hitherto append-only state of the world. 2020-12-09 Added a deletion endpoint to the Images endpoint, allowing you to delete unused images. ", "title": "Changelog", "description": "Everything that's new in Buttondown's API" }, - { - "path": "/api-reference/emails/", - "text": "Emails ", - "title": "Emails", - "description": "An API reference for the 'email' object in Buttondown" - }, { "path": "/api-reference/events-and-webhooks", "text": "Events and Webhooks Events are Buttondown's way of telling you that something interesting has happened to your newsletter, and webhooks are Buttondown's\nway of letting you react to various incoming events. For example, when a new subscriber signs up for your newsletter, Buttondown creates\nand emits a event; when that subscriber unsubscribes, Buttondown creates a event. If you wanted\nto, say, create a Slack notification or run some application code whenever something in Buttondown happens, you'd create a webhook for the concomitant\nevent and point it towards your application. The payload of most events are fairly simple, and look something like this: Events ", "title": "Events and webhooks", "description": "How to get a full lens of your Buttondown newsletter through structured data" }, - { - "path": "/api-reference/exports/", - "text": "Exports ", - "title": "Exports", - "description": "An API reference for the 'export' object in Buttondown" - }, - { - "path": "/api-reference/images/", - "text": "Images ", - "title": "Images", - "description": "An API reference for the 'image' object in Buttondown" - }, { "path": "/api-reference/introduction", "text": "Introduction Hello and welcome to Buttondown's API schema! I've designed Buttondown's API to be as RESTful and uninteresting as possible: if there's a primitive in Buttondown, you should have a nice interface for it, with the ability to retrieve, create, modify, and delete. This means that with a few lines in your language of choice, you're able to programmatically keep track of your subscribers, send out new emails, and more. If you're confused about what Buttondown is, try going to buttondown.email . If you're looking for where to find your API Key, check your settings page . Whenever Buttondown's API changes, those changes will be posted on the changelog and on Twitter . Keeping our API reference accurate, up-to-date and easy to understand is really important. If you have any questions, spot any mistakes or want to give us any feedback, just drop me a line! Lastly, if you'd like to just download the OpenAPI spec for Buttondown, you can do so here . ", "title": "Introduction", "description": "An introduction to thinking about Buttondown's API" }, - { - "path": "/api-reference/newsletters/", - "text": "Newsletter ", - "title": "Newsletters", - "description": "An API reference for the 'newsletter' object in Buttondown" - }, - { - "path": "/api-reference/subscribers/", - "text": "Subscribers ", - "title": "Subscribers", - "description": "An API reference for the 'subscriber' object in Buttondown" - }, - { - "path": "/api-reference/tags/", - "text": "Tags ", - "title": "Tags", - "description": "An API reference for the 'tag' object in Buttondown" - }, { "path": "/behind-the-scenes/esoterica", "text": "Esoterica My favorite newsletters Want to read some of my favorites for inspiration? (Don't worry, they're not my favorites just because they're on Buttondown — as you'll see from some of the links!) Money Stuff by Matt Levine Dept. of Enthusiasm by Jez Burrows Cryptography Dispatches by Filippo Valsorda Letters to Summer by Summer and Jillian ",