Publish content with an atomic price and visibility policy
This commit is contained in:
@@ -400,34 +400,26 @@ async function save() {
|
||||
(i) => matchesThisFile(i.filename)
|
||||
)?.id
|
||||
|
||||
// Add if not in catalog
|
||||
const policy: Record<string, unknown> = { access: accessType.value, availability: 'all_peers' }
|
||||
if (accessType.value === 'paid') {
|
||||
policy.price_sats = priceSats.value
|
||||
policy.accepted_methods = [...acceptedSet.value]
|
||||
}
|
||||
if (!itemId) {
|
||||
const ext = props.filename.split('.').pop()?.toLowerCase() || ''
|
||||
const addRes = await rpcClient.call<{ item: { id: string } }>({
|
||||
method: 'content.add',
|
||||
await rpcClient.call({
|
||||
method: 'content.publish',
|
||||
params: {
|
||||
filename: (props.filepath || props.filename).replace(/^\/+/, ''),
|
||||
mime_type: SHARE_MIME_MAP[ext] || 'application/octet-stream',
|
||||
description: '',
|
||||
...policy,
|
||||
},
|
||||
})
|
||||
itemId = addRes.item.id
|
||||
} else {
|
||||
await rpcClient.call({ method: 'content.configure', params: { id: itemId, ...policy } })
|
||||
}
|
||||
|
||||
// Set pricing
|
||||
const pricingParams: Record<string, unknown> = { id: itemId, access: accessType.value }
|
||||
if (accessType.value === 'paid') {
|
||||
pricingParams.price_sats = priceSats.value
|
||||
pricingParams.accepted_methods = [...acceptedSet.value]
|
||||
}
|
||||
await rpcClient.call({ method: 'content.set-pricing', params: pricingParams })
|
||||
|
||||
// Set availability to all peers
|
||||
await rpcClient.call({
|
||||
method: 'content.set-availability',
|
||||
params: { id: itemId, availability: 'all_peers' },
|
||||
})
|
||||
|
||||
const label =
|
||||
accessType.value === 'paid'
|
||||
? `Shared for ${priceSats.value} sats`
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { beforeEach, afterEach, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import ShareModal from '../ShareModal.vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
vi.mock('@/api/rpc-client', () => ({ rpcClient: { call: vi.fn() } }))
|
||||
beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) })
|
||||
afterEach(() => { vi.clearAllTimers(); vi.useRealTimers() })
|
||||
|
||||
it.each([false, true])('publishes one complete policy for an existing share: %s', async existing => {
|
||||
vi.mocked(rpcClient.call).mockImplementation(async ({ method }) => {
|
||||
if (method === 'content.list-mine') return { items: existing ? [{ id: 'saved-id', filename: 'film.mp4', access: 'free' }] : [] }
|
||||
return { item: { id: 'new-id' } }
|
||||
})
|
||||
const wrapper = mount(ShareModal, { props: { filename: 'film.mp4', filepath: '/film.mp4', isDir: false } })
|
||||
await flushPromises()
|
||||
const vm = (wrapper.vm as any).$.setupState
|
||||
vm.shared = true; vm.accessType = 'paid'; vm.priceSats = 1; vm.acceptedSet = new Set(['ecash'])
|
||||
await vm.save()
|
||||
const writes = vi.mocked(rpcClient.call).mock.calls.map(([call]) => call).filter(call => ['content.publish','content.configure','content.set-pricing','content.set-availability'].includes(call.method))
|
||||
expect(writes).toHaveLength(1)
|
||||
expect(writes[0]?.method).toBe(existing ? 'content.configure' : 'content.publish')
|
||||
expect(writes[0]?.params).toMatchObject({ access: 'paid', price_sats: 1, accepted_methods: ['ecash'], availability: 'all_peers' })
|
||||
expect(writes[0]?.params).toMatchObject(existing ? { id: 'saved-id' } : { filename: 'film.mp4' })
|
||||
wrapper.unmount()
|
||||
})
|
||||
@@ -467,27 +467,16 @@ async function addContentItem() {
|
||||
addContentError.value = ''
|
||||
try {
|
||||
await rpcClient.call({
|
||||
method: 'content.add',
|
||||
method: 'content.publish',
|
||||
params: {
|
||||
filename: newContentFilename.value.trim(),
|
||||
mime_type: newContentMimeType.value.trim() || 'application/octet-stream',
|
||||
description: newContentDescription.value.trim(),
|
||||
access: newContentAccess.value,
|
||||
availability: 'all_peers',
|
||||
...(newContentAccess.value === 'paid' ? { price_sats: newContentPrice.value || 100 } : {}),
|
||||
},
|
||||
})
|
||||
if (newContentAccess.value !== 'free') {
|
||||
const items = (await rpcClient.call<{ items: ContentItemData[] }>({ method: 'content.list-mine' })).items || []
|
||||
const latest = items[items.length - 1]
|
||||
if (latest) {
|
||||
await rpcClient.call({
|
||||
method: 'content.set-pricing',
|
||||
params: {
|
||||
id: latest.id,
|
||||
access: newContentAccess.value,
|
||||
...(newContentAccess.value === 'paid' ? { price_sats: newContentPrice.value || 100 } : {}),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
showAddContentModal.value = false
|
||||
newContentFilename.value = ''
|
||||
newContentMimeType.value = 'application/octet-stream'
|
||||
|
||||
@@ -116,3 +116,17 @@ describe('Web5SharedContent', () => {
|
||||
expect(wrapper.text()).not.toContain('Refreshing peer content...')
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a paid Web5 share with its price in the first write', async () => {
|
||||
vi.mocked(rpcClient.call).mockReset()
|
||||
vi.mocked(rpcClient.call).mockResolvedValue({ items: [] })
|
||||
const wrapper = mount(Web5SharedContent, { props: { showStagger: false, peers: [] } })
|
||||
await flushPromises()
|
||||
const vm = (wrapper.vm as any).$.setupState
|
||||
vm.newContentFilename = 'film.mp4'; vm.newContentAccess = 'paid'; vm.newContentPrice = 1
|
||||
await vm.addContentItem()
|
||||
const writes = vi.mocked(rpcClient.call).mock.calls.map(([call]) => call).filter(call => ['content.publish','content.set-pricing'].includes(call.method))
|
||||
expect(writes).toHaveLength(1)
|
||||
expect(writes[0]).toMatchObject({ method: 'content.publish', params: { filename: 'film.mp4', access: 'paid', price_sats: 1, availability: 'all_peers' } })
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user