Skip to content

Commit

Permalink
feat: change plan/update to plan/set and use existing `PlansStora…
Browse files Browse the repository at this point in the history
…ge#set` to implement an invocation handler (#1258)

We already have a method on the `PlansStorage` interface that has the
semantics we want - rename `plan/update` to `plan/set` and add a handler
implementation that hands off to the `PlanStorage#set` method.
  • Loading branch information
travis authored Jan 17, 2024
1 parent 7f42f39 commit 1ccbfe9
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 30 deletions.
4 changes: 2 additions & 2 deletions packages/capabilities/src/plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export const get = capability({
/**
* Capability can be invoked by an account to change its billing plan.
*/
export const update = capability({
can: 'plan/update',
export const set = capability({
can: 'plan/set',
with: AccountDID,
nb: struct({
product: DID,
Expand Down
6 changes: 3 additions & 3 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,9 +630,9 @@ export interface PlanNotFound extends Ucanto.Failure {

export type PlanGetFailure = PlanNotFound

export type PlanUpdate = InferInvokedCapability<typeof PlanCaps.update>
export type PlanSet = InferInvokedCapability<typeof PlanCaps.set>

export type PlanUpdateSuccess = Unit
export type PlanSetSuccess = Unit

export interface AccountNotFound extends Ucanto.Failure {
name: 'AccountNotFound'
Expand All @@ -642,7 +642,7 @@ export interface InvalidPlanName extends Ucanto.Failure {
name: 'InvalidPlanName'
}

export type PlanUpdateFailure = AccountNotFound
export type PlanSetFailure = AccountNotFound

// Top
export type Top = InferInvokedCapability<typeof top>
Expand Down
44 changes: 22 additions & 22 deletions packages/capabilities/test/capabilities/plan.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ describe('plan/get', function () {
})
})

describe('plan/update', function () {
describe('plan/set', function () {
const agent = alice
const account = 'did:mailto:mallory.com:mallory'
it('can invoke as an account', async function () {
const auth = Plan.update.invoke({
const auth = Plan.set.invoke({
issuer: agent,
audience: service,
with: account,
Expand All @@ -109,7 +109,7 @@ describe('plan/update', function () {
proofs: await createAuthorization({ agent, service, account }),
})
const result = await access(await auth.delegate(), {
capability: Plan.update,
capability: Plan.set,
principal: Verifier,
authority: service,
validateAuthorization,
Expand All @@ -118,14 +118,14 @@ describe('plan/update', function () {
assert.fail(`error in self issue: ${result.error.message}`)
} else {
assert.deepEqual(result.ok.audience.did(), service.did())
assert.equal(result.ok.capability.can, 'plan/update')
assert.equal(result.ok.capability.can, 'plan/set')
assert.deepEqual(result.ok.capability.with, account)
}
})

it('fails without account delegation', async function () {
const agent = alice
const auth = Plan.update.invoke({
const auth = Plan.set.invoke({
issuer: agent,
audience: service,
with: account,
Expand All @@ -135,7 +135,7 @@ describe('plan/update', function () {
})

const result = await access(await auth.delegate(), {
capability: Plan.update,
capability: Plan.set,
principal: Verifier,
authority: service,
validateAuthorization,
Expand All @@ -145,7 +145,7 @@ describe('plan/update', function () {
})

it('fails when invoked by a different agent', async function () {
const auth = Plan.update.invoke({
const auth = Plan.set.invoke({
issuer: bob,
audience: service,
with: account,
Expand All @@ -156,24 +156,24 @@ describe('plan/update', function () {
})

const result = await access(await auth.delegate(), {
capability: Plan.update,
capability: Plan.set,
principal: Verifier,
authority: service,
validateAuthorization,
})
assert.equal(result.error?.message.includes('not authorized'), true)
})

it('can delegate plan/update', async function () {
const invocation = Plan.update.invoke({
it('can delegate plan/set', async function () {
const invocation = Plan.set.invoke({
issuer: bob,
audience: service,
with: account,
nb: {
product: 'did:web:lite.web3.storage',
},
proofs: [
await Plan.update.delegate({
await Plan.set.delegate({
issuer: agent,
audience: bob,
with: account,
Expand All @@ -182,7 +182,7 @@ describe('plan/update', function () {
],
})
const result = await access(await invocation.delegate(), {
capability: Plan.update,
capability: Plan.set,
principal: Verifier,
authority: service,
validateAuthorization,
Expand All @@ -191,21 +191,21 @@ describe('plan/update', function () {
assert.fail(`error in self issue: ${result.error.message}`)
} else {
assert.deepEqual(result.ok.audience.did(), service.did())
assert.equal(result.ok.capability.can, 'plan/update')
assert.equal(result.ok.capability.can, 'plan/set')
assert.deepEqual(result.ok.capability.with, account)
}
})

it('can invoke plan/update with the product that its delegation specifies', async function () {
const invocation = Plan.update.invoke({
it('can invoke plan/set with the product that its delegation specifies', async function () {
const invocation = Plan.set.invoke({
issuer: bob,
audience: service,
with: account,
nb: {
product: 'did:web:lite.web3.storage',
},
proofs: [
await Plan.update.delegate({
await Plan.set.delegate({
issuer: agent,
audience: bob,
with: account,
Expand All @@ -217,7 +217,7 @@ describe('plan/update', function () {
],
})
const result = await access(await invocation.delegate(), {
capability: Plan.update,
capability: Plan.set,
principal: Verifier,
authority: service,
validateAuthorization,
Expand All @@ -226,21 +226,21 @@ describe('plan/update', function () {
assert.fail(`error in self issue: ${result.error.message}`)
} else {
assert.deepEqual(result.ok.audience.did(), service.did())
assert.equal(result.ok.capability.can, 'plan/update')
assert.equal(result.ok.capability.can, 'plan/set')
assert.deepEqual(result.ok.capability.with, account)
}
})

it('cannot invoke plan/update with a different product than its delegation specifies', async function () {
const invocation = Plan.update.invoke({
it('cannot invoke plan/set with a different product than its delegation specifies', async function () {
const invocation = Plan.set.invoke({
issuer: bob,
audience: service,
with: account,
nb: {
product: 'did:web:lite.web3.storage',
},
proofs: [
await Plan.update.delegate({
await Plan.set.delegate({
issuer: agent,
audience: bob,
with: account,
Expand All @@ -252,7 +252,7 @@ describe('plan/update', function () {
],
})
const result = await access(await invocation.delegate(), {
capability: Plan.update,
capability: Plan.set,
principal: Verifier,
authority: service,
validateAuthorization,
Expand Down
18 changes: 18 additions & 0 deletions packages/upload-api/src/plan/set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as API from '../types.js'
import * as Provider from '@ucanto/server'
import { Plan } from '@web3-storage/capabilities'

/**
* @param {API.PlanServiceContext} context
*/
export const provide = (context) =>
Provider.provide(Plan.set, (input) => set(input, context))

/**
* @param {API.Input<Plan.set>} input
* @param {API.PlanServiceContext} context
* @returns {Promise<API.Result<API.PlanSetSuccess, API.PlanSetFailure>>}
*/
const set = async ({ capability }, context) => {
return context.plansStorage.set(capability.with, capability.nb.product)
}
7 changes: 4 additions & 3 deletions packages/upload-api/src/types/plans.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Ucanto from '@ucanto/interface'
import { AccountDID, DID, PlanGetFailure, PlanGetSuccess } from '../types.js'
import { AccountDID, DID, PlanGetFailure, PlanGetSuccess, PlanSetFailure, PlanSetSuccess } from '../types.js'

export type PlanID = DID

Expand All @@ -17,12 +17,13 @@ export interface PlansStorage {
) => Promise<Ucanto.Result<PlanGetSuccess, PlanGetFailure>>

/**
* Set an account's plan
* Set an account's plan. Update our systems and any third party billing systems.
*
* @param account account DID
* @param plan the DID of the new plan
*/
set: (
account: AccountDID,
plan: DID
) => Promise<Ucanto.Result<Ucanto.Unit, Ucanto.Failure>>
) => Promise<Ucanto.Result<PlanSetSuccess, PlanSetFailure>>
}

0 comments on commit 1ccbfe9

Please sign in to comment.