v0.1.0:24 — Keysat licensing service end-to-end
Daemon, StartOS wrapper, admin SPA, public buy/thank-you pages, discount codes, free-license redemption, Apply-discount UX, self-licensing, and v0.1.0 release notes.
This commit is contained in:
@@ -4,33 +4,34 @@
|
||||
// key. No need for the operator to touch curl or handle tokens.
|
||||
|
||||
import { sdk } from '../sdk'
|
||||
import { store } from '../fileModels/store'
|
||||
import { adminCall, LICENSING_URL } from '../utils'
|
||||
|
||||
const input = sdk.InputSpec.of({
|
||||
slug: {
|
||||
type: 'text',
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
const input = InputSpec.of({
|
||||
slug: Value.text({
|
||||
name: 'Slug',
|
||||
description: 'URL-safe short name, e.g., "my-app". Used in product links.',
|
||||
required: true,
|
||||
default: null,
|
||||
patterns: [{ regex: '^[a-z0-9-]{2,40}$', description: 'lowercase letters, digits, and dashes' }],
|
||||
},
|
||||
name: {
|
||||
type: 'text',
|
||||
patterns: [
|
||||
{ regex: '^[a-z0-9-]{2,40}$', description: 'lowercase letters, digits, and dashes' },
|
||||
],
|
||||
}),
|
||||
name: Value.text({
|
||||
name: 'Name',
|
||||
description: 'Display name shown to buyers.',
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
description: {
|
||||
type: 'textarea',
|
||||
}),
|
||||
description: Value.textarea({
|
||||
name: 'Description',
|
||||
description: 'Public description of what the buyer is getting.',
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
price_sats: {
|
||||
type: 'number',
|
||||
}),
|
||||
price_sats: Value.number({
|
||||
name: 'Price (sats)',
|
||||
description: 'Price per license in satoshis. 100,000,000 sats = 1 BTC.',
|
||||
required: true,
|
||||
@@ -38,12 +39,12 @@ const input = sdk.InputSpec.of({
|
||||
min: 1,
|
||||
max: null,
|
||||
integer: true,
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
export const createProduct = sdk.Action.withInput(
|
||||
'createProduct',
|
||||
async ({ effects }) => ({
|
||||
'create-product',
|
||||
async () => ({
|
||||
name: 'Create product',
|
||||
description: 'Add a new product that can be purchased through this service.',
|
||||
warning: null,
|
||||
@@ -52,9 +53,11 @@ export const createProduct = sdk.Action.withInput(
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
input,
|
||||
async ({ effects, input: formInput }) => {
|
||||
const store = await sdk.store.getOwn(effects, sdk.StorePath).const()
|
||||
const resp = await adminCall(LICENSING_URL, store.admin_api_key, '/v1/admin/products', {
|
||||
async () => null,
|
||||
async ({ effects: _effects, input: formInput }) => {
|
||||
const storeData = await store.read().once()
|
||||
if (!storeData) throw new Error('Store not initialized — restart the service.')
|
||||
const resp = await adminCall(LICENSING_URL, storeData.admin_api_key, '/v1/admin/products', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
slug: formInput.slug,
|
||||
@@ -67,13 +70,16 @@ export const createProduct = sdk.Action.withInput(
|
||||
if (!resp.ok) {
|
||||
throw new Error(`Create product failed: HTTP ${resp.status} — ${await resp.text()}`)
|
||||
}
|
||||
const body = await resp.json()
|
||||
const body = (await resp.json()) as { id: string; slug: string; price_sats: number }
|
||||
return {
|
||||
version: '1',
|
||||
title: 'Product created',
|
||||
message:
|
||||
`Created product '${body.slug}' (id ${body.id}).\n` +
|
||||
`Priced at ${body.price_sats} sats.\n\n` +
|
||||
`Buyers can purchase by POSTing to your Keysat URL:\n` +
|
||||
`<your Keysat URL>/v1/purchase with body: {"product":"${body.slug}"}`,
|
||||
result: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user