Skip to content

Commit

Permalink
Added programatic docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ntotten committed Feb 21, 2024
1 parent 44ca439 commit aacc02f
Show file tree
Hide file tree
Showing 30 changed files with 270 additions and 129 deletions.
7 changes: 3 additions & 4 deletions .github/ISSUE_TEMPLATE/policies-doc.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
---
name: Policies Doc
about: File a bug or suggestion for a policy
title: ''
labels: ''
assignees: ''

title: ""
labels: ""
assignees: ""
---

<!-- Describe the issue or suggestion for the policy -->
12 changes: 2 additions & 10 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,9 @@
// language - current active spelling language
"language": "en",
// words - list of words to be always considered correct
"words": [
"Kubernetes",
"Linkerd",
"Quickstart",
"quickstarts",
"Zuplo"
],
"words": ["Kubernetes", "Linkerd", "Quickstart", "quickstarts", "Zuplo"],
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
// For example "hte" should be "the"
"flagWords": [
"hte"
]
"flagWords": ["hte"]
}
2 changes: 1 addition & 1 deletion docs/articles/background-dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The `options.msDelay` parameter is required and must be a valid non-zero number.
```ts
const backgroundDispatcher = new BackgroundDispatcher<TestEntry>(
dispatchFunction,
{ msDelay: 100 }
{ msDelay: 100 },
);
```

Expand Down
6 changes: 3 additions & 3 deletions docs/articles/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export async function tracingPlugin(
request: ZuploRequest,
context: ZuploContext,
policyName: string
policyName: string,
) {
// Get the trace header
let traceparent = request.headers.get("traceparent");
Expand Down Expand Up @@ -71,7 +71,7 @@ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
export async function pluginWithHook(
request: ZuploRequest,
context: ZuploContext,
policyName: string
policyName: string,
) {
const cloned = request.clone();
context.addResponseSendingFinalHook(
Expand All @@ -81,7 +81,7 @@ export async function pluginWithHook(
method: "GET",
body,
});
}
},
);

return request;
Expand Down
3 changes: 2 additions & 1 deletion docs/articles/monetization-glossary.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
title: Monetization Terms Glossary
title: Monetization Glossary
sidebar_label: Glossary
---
88 changes: 88 additions & 0 deletions docs/articles/monetization-programmatic-quotas.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,91 @@
---
title: Monetization Programmatic Quotas
sidebar_label: Programmatic Quotas
---

Typically, when adding monetization to your API, you set the number of meters a
request will consume in the settings of the
[Monetization Policy](https://zuplo.com/docs/policies/monetization-inbound). For
example, the policy below specifies that the request will consume 1 `requests`
meter and 5 `computeUnits` meters.

```json
{
"export": "MonetizationInboundPolicy",
"module": "$import(@zuplo/runtime)",
"options": {
"allowRequestsOverQuota": false,
"allowedSubscriptionStatuses": ["active", "incomplete"],
"meterOnStatusCodes": "200-399",
"meters": {
"requests": 1,
"computeUnits": 5
}
}
}
```

However, in some cases, you may not know up front how many units of a particular
meter will be consumed until after the response is sent. For example, maybe your
backend is responsible for computing the `computeUnits` on a request and send
the result in the response in the `compute-units` header.

In Zuplo, you can support these dynamic meters by writing a little code. To make
the `computeUnits` meter dynamic, first update the policy by setting the
`computeUnits` meter to `0` as shown below.

```json
{
"export": "MonetizationInboundPolicy",
"module": "$import(@zuplo/runtime)",
"options": {
"allowRequestsOverQuota": false,
"allowedSubscriptionStatuses": ["active", "incomplete"],
"meterOnStatusCodes": "200-399",
"meters": {
"requests": 1,
"computeUnits": 0
}
}
}
```

Next you can create a
[custom code outbound policy](/docs/policies/custom-code-outbound) that reads
data from the Response (in this case the `compute-units` header) and sets the
meter programmatically.

```ts title="/modules/set-compute-units-outbound.ts"
import {
MonetizationInboundPolicy,
ZuploRequest,
ZuploContext,
} from "@zuplo/runtime";

export default async function (
response: Response,
request: ZuploRequest,
context: ZuploContext,
options: any,
policyName: string,
) {
const headerValue = response.headers.get("compute-units");
let computeUnits;
if (headerValue && typeof headerValue === "string") {
computeUnits = parseInt(headerValue);
}

// Throw an error if the server doesn't send compute units
// Alternatively, you could have a default value
if (!computeUnits) {
throw new Error("Invalid response, no compute units sent.");
}

// Set the compute units for the request
MonetizationInboundPolicy.setMeters(context, {
computeUnits,
});

return response;
}
```
54 changes: 54 additions & 0 deletions docs/articles/stripe-monetization-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: Stripe Monetization Plugin
sidebar_label: Stripe Plugin
---

The Stripe Monetization Plugin makes makes it easy to register a Stripe Webhook
in your Zuplo API that will handle Stripe subscription events.

When you register the Stripe Plugin a new route is configured on your API at the
path `/__plugins/stripe/webhook`. This route is used to receive webhooks sent by
stripe for Stripe subscription events.

The Plugin is registered in the `zuplo.runtime.ts` extension. It requires
setting the `webhooks.signingSecret` value and the `stripeSecretKey` in order to
function.

There is additional configuration if you wan to customize the path, etc, but for
most cases no additional configuration is required.

```ts
import {
RuntimeExtensions,
StripeMonetizationPlugin,
environment,
} from "@zuplo/runtime";

export function runtimeInit(runtime: RuntimeExtensions) {
// Create the Stripe Plugin
const stripe = new StripeMonetizationPlugin({
webhooks: {
signingSecret: environment.STRIPE_WEBHOOK_SIGNING_SECRET,
},
stripeSecretKey: environment.STRIPE_SECRET_KEY,
});
// Register the plugin
runtime.addPlugin(stripe);
}
```

## Debugging & Troubleshooting

The Runtime Plugin emits logs that show what the Webhook is doing. For example,
when a new subscription is created, the plugin will log information about the
Stripe subscription, user, etc.

If are having trouble with the Webhooks, reviewing the logs for the Plugin is
the place to start.

Additionally, you can use the Stripe
[Webhook logs](https://dashboard.stripe.com/test/webhooks) in the Stripe
Dashboard to view the webhooks that were send and their status. You can also
resend a webhook event.

![Stripe Webhooks](../../public/media/stripe-monetization-plugin/image.png)
13 changes: 7 additions & 6 deletions docs/articles/support.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ offers access to the following channels:
### Premium support

Customers on a Zuplo enterprise plan can choose from premium support offerings
that can optionally include specific SLAs for response time as well as additional means of
contact such as a private Slack channel.
that can optionally include specific SLAs for response time as well as
additional means of contact such as a private Slack channel.

As part of premium support, Zuplo can also offer:

Expand All @@ -58,7 +58,7 @@ As part of premium support, Zuplo can also offer:
- Advice on best-practices designing your Zuplo API
- Troubleshooting

Contact sales to explore adding these options to your agreement.
Contact sales to explore adding these options to your agreement.

## Contact Methods

Expand Down Expand Up @@ -90,9 +90,10 @@ your support offering.

### Private Discord/Slack Channel

Enterprise support contracts can optionally chat directly with the Zuplo team in a private
Discord or Slack channel. These channels are useful for posting feature
requests, asking questions, or general troubleshooting. Contact sales for more info.
Enterprise support contracts can optionally chat directly with the Zuplo team in
a private Discord or Slack channel. These channels are useful for posting
feature requests, asking questions, or general troubleshooting. Contact sales
for more info.

:::caution

Expand Down
2 changes: 1 addition & 1 deletion docs/articles/zuplo-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ of the file is `1`.
{
"version": 1,
"project": "my-project",
"compatibilityDate": "2023-03-14"
"compatibilityDate": "2023-03-14",
}
```

Expand Down
7 changes: 3 additions & 4 deletions policies/custom-code-outbound/doc.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
:::tip
The outbound policy will only execute if the response status code is 'ok'
(e.g. `response.ok === true` or the status code is 200-299) - see
[response.ok on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok).
:::tip The outbound policy will only execute if the response status code is 'ok'
(e.g. `response.ok === true` or the status code is 200-299) - see
[response.ok on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok).
:::

### Writing A Policy
Expand Down
4 changes: 2 additions & 2 deletions policies/rbac-policy-inbound/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export default async function (

// Check that the user has one of the allowed roles
if (
!options.allowedRoles.some(
(allowedRole) => request.user?.data.roles.includes(allowedRole),
!options.allowedRoles.some((allowedRole) =>
request.user?.data.roles.includes(allowedRole),
)
) {
context.log.error(
Expand Down
2 changes: 1 addition & 1 deletion postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"label": "Reference",
"items": [
"articles/monetization-glossary",
"articles/monetization-programmatic-quotas"
"articles/monetization-programmatic-quotas",
"articles/stripe-monetization-plugin"
]
}
]
Expand Down
4 changes: 2 additions & 2 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Link from 'next/link'
import Link from "next/link";

export default function NotFound() {
return (
Expand All @@ -21,5 +21,5 @@ export default function NotFound() {
</Link>
</div>
</div>
)
);
}
24 changes: 12 additions & 12 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import Link from 'next/link'
import clsx from 'clsx'
import Link from "next/link";
import clsx from "clsx";

const variantStyles = {
primary:
'rounded-full bg-pink py-2 px-4 text-sm font-semibold text-gray-900 hover:bg-pink-HOVER focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink/50 active:bg-pink-HOVER',
"rounded-full bg-pink py-2 px-4 text-sm font-semibold text-gray-900 hover:bg-pink-HOVER focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink/50 active:bg-pink-HOVER",
secondary:
'rounded-full bg-gray-800 py-2 px-4 text-sm font-medium text-white hover:bg-gray-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-gray-400',
}
"rounded-full bg-gray-800 py-2 px-4 text-sm font-medium text-white hover:bg-gray-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-gray-400",
};

type ButtonProps = {
variant?: keyof typeof variantStyles
variant?: keyof typeof variantStyles;
} & (
| React.ComponentPropsWithoutRef<typeof Link>
| (React.ComponentPropsWithoutRef<'button'> & { href?: undefined })
)
| (React.ComponentPropsWithoutRef<"button"> & { href?: undefined })
);

export function Button({
variant = 'primary',
variant = "primary",
className,
...props
}: ButtonProps) {
className = clsx(variantStyles[variant], className)
className = clsx(variantStyles[variant], className);

return typeof props.href === 'undefined' ? (
return typeof props.href === "undefined" ? (
<button className={className} {...props} />
) : (
<Link className={className} {...props} />
)
);
}
8 changes: 4 additions & 4 deletions src/components/HeroBackground.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useId } from 'react'
import { useId } from "react";

export function HeroBackground(props: React.ComponentPropsWithoutRef<'svg'>) {
let id = useId()
export function HeroBackground(props: React.ComponentPropsWithoutRef<"svg">) {
let id = useId();

return (
<svg
Expand Down Expand Up @@ -184,5 +184,5 @@ export function HeroBackground(props: React.ComponentPropsWithoutRef<'svg'>) {
/>
</g>
</svg>
)
);
}
Loading

0 comments on commit aacc02f

Please sign in to comment.