Skip to content
This repository has been archived by the owner on Dec 27, 2022. It is now read-only.

Commit

Permalink
Add owned-items-idx
Browse files Browse the repository at this point in the history
  • Loading branch information
pfrazee committed Mar 17, 2021
1 parent 0613b7d commit b02b9cc
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 9 deletions.
45 changes: 44 additions & 1 deletion db/citizen.js
Expand Up @@ -19,20 +19,63 @@ export class PublicCitizenDB extends BaseHyperbeeDB {
async setup () {
await super.setup()
await this.blobs.setup()
this.indexState = this.getTable('ctzn.network/index-state')
this.dbmethodCalls = this.getTable('ctzn.network/dbmethod-call')
this.profile = this.getTable('ctzn.network/profile')
this.posts = this.getTable('ctzn.network/post')
this.comments = this.getTable('ctzn.network/comment')
this.reactions = this.getTable('ctzn.network/reaction')
this.follows = this.getTable('ctzn.network/follow')
this.memberships = this.getTable('ctzn.network/community-membership')
this.ownedItemsIndex = this.getTable('ctzn.network/owned-items-idx')

this.memberships.onPut(() => {
this.emit('subscriptions-changed')
})
this.memberships.onDel(() => {
this.emit('subscriptions-changed')
})

this.createIndexer('ctzn.network/owned-items-idx', ['ctzn.network/item'], async (db, change) => {
const pend = perf.measure(`publicUserDb:owned-items-indexer`)

const newOwner = change.value?.owner
const oldEntry = await db.bee.checkout(change.seq).get(change.key, {timeout: 10e3})
const oldOwner = oldEntry?.value?.owner
if (newOwner?.dbUrl !== this.url && oldOwner?.dbUrl !== this.url) {
return // not our item
}
if (newOwner?.dbUrl === oldOwner?.dbUrl) {
return // no change in ownership, dont need to process it
}
const itemUrl = constructEntryUrl(db.url, change.keyParsed.schemaId, change.keyParsed.key)

const release = await this.lock(`owned-items-idx:${itemUrl}`)
try {
const key = `${db.userId}:${change.keyParsed.key}`
if (oldOwner?.dbUrl === this.url) {
// we're the old owner, item is now gone
await this.ownedItemsIndex.del(key)
} else if (newOwner?.dbUrl === this.url) {
// we're the new owner, item is added
await this.ownedItemsIndex.put(key, {
item: {
key: change.keyParsed.key,
userId: db.userId,
dbUrl: itemUrl
},
createdAt: (new Date()).toISOString()
})
}
} finally {
release()
pend()
}
})
}

async getSubscribedDbUrls () {
return (await this.memberships.list()).map(entry => entry.value.community.dbUrl)
}
}

Expand Down Expand Up @@ -100,7 +143,7 @@ export class PrivateCitizenDB extends BaseHyperbeeDB {
}

const createdAt = new Date()
await this.notificationsIdx.put(mlts(Math.min(createdAt, itemCreatedAt || createdAt)), {
await this.notificationsIdx.put(mlts(Math.min(+createdAt, (+itemCreatedAt) || (+createdAt))), {
itemUrl: constructEntryUrl(db.url, change.keyParsed.schemaId, change.keyParsed.key),
createdAt: createdAt.toISOString()
})
Expand Down
3 changes: 2 additions & 1 deletion db/community.js
Expand Up @@ -45,6 +45,7 @@ export class PublicCommunityDB extends BaseHyperbeeDB {
this.bans = this.getTable('ctzn.network/community-ban')
this.itemClasses = this.getTable('ctzn.network/item-class')
this.items = this.getTable('ctzn.network/item')
this.ownedItemsIndex = this.getTable('ctzn.network/owned-items-idx')
this.indexState = this.getTable('ctzn.network/index-state')
this.feedIdx = this.getTable('ctzn.network/feed-idx')
this.threadIdx = this.getTable('ctzn.network/thread-idx')
Expand Down Expand Up @@ -75,7 +76,7 @@ export class PublicCommunityDB extends BaseHyperbeeDB {
createdAt: createdAt.toISOString()
}
const createKey = (url, itemCreatedAt) => {
return `${hyperUrlToKeyStr(url)}:${mlts(Math.min(createdAt, itemCreatedAt || createdAt))}`
return `${hyperUrlToKeyStr(url)}:${mlts(Math.min(+createdAt, itemCreatedAt || createdAt))}`
}
try {
switch (change.keyParsed.schemaId) {
Expand Down
17 changes: 17 additions & 0 deletions db/dbmethods/_util.js
Expand Up @@ -14,4 +14,21 @@ export async function assertUserPermission (publicCommunityDb, userId, permId) {
}
}
throw new errors.PermissionsError(`Permission denied: ${permId}`)
}

export async function addOwnedItemsIdx (db, itemKey, itemUrl) {
const key = `${db.userId}:${itemKey}`
await db.ownedItemsIndex.put(key, {
item: {
key: itemKey,
userId: db.userId,
dbUrl: itemUrl
},
createdAt: (new Date()).toISOString()
})
}

export async function delOwnedItemsIdx (db, itemKey) {
const key = `${db.userId}:${itemKey}`
await db.ownedItemsIndex.del(key)
}
11 changes: 9 additions & 2 deletions db/dbmethods/create-item.js
@@ -1,4 +1,5 @@
import { assertUserPermission } from './_util.js'
import { assertUserPermission, addOwnedItemsIdx } from './_util.js'
import { onDatabaseChange } from '../index.js'
import * as errors from '../../lib/errors.js'
import { compileKeyGenerator } from '../../lib/schemas.js'

Expand Down Expand Up @@ -46,8 +47,14 @@ export default async function (db, caller, args) {
release()
}

return {
const res ={
key,
url: db.items.constructEntryUrl(key)
}
if (value.owner.dbUrl === db.url) {
await addOwnedItemsIdx(db, key, res.url)
}
await onDatabaseChange(db)

return res
}
7 changes: 6 additions & 1 deletion db/dbmethods/destroy-item.js
@@ -1,4 +1,5 @@
import { assertUserPermission } from './_util.js'
import { assertUserPermission, delOwnedItemsIdx } from './_util.js'
import { onDatabaseChange } from '../index.js'
import * as errors from '../../lib/errors.js'

export default async function (db, caller, args) {
Expand All @@ -20,7 +21,11 @@ export default async function (db, caller, args) {
await db.items.put(itemEntry.key, itemEntry.value)
} else {
await db.items.del(itemEntry.key)
if (itemEntry.value.owner.dbUrl === db.url) {
await delOwnedItemsIdx(db, itemEntry.key)
}
}
await onDatabaseChange(db)
} finally {
release()
}
Expand Down
18 changes: 17 additions & 1 deletion db/dbmethods/transfer-item.js
@@ -1,4 +1,5 @@
import { assertUserPermission } from './_util.js'
import { assertUserPermission, addOwnedItemsIdx, delOwnedItemsIdx } from './_util.js'
import { onDatabaseChange } from '../index.js'
import * as errors from '../../lib/errors.js'
import { compileKeyGenerator } from '../../lib/schemas.js'

Expand Down Expand Up @@ -47,6 +48,12 @@ export default async function (db, caller, args) {
// not a divisible item, just transfer ownership
destValue.qty = sourceItemEntry.value.qty
await db.items.put(destKey, destValue)
if (destValue.owner.dbUrl === db.url) {
await addOwnedItemsIdx(db, destKey, db.items.constructEntryUrl(destKey))
} else if (sourceItemEntry.value.owner.userId === db.url) {
await delOwnedItemsIdx(db, destKey)
}
await onDatabaseChange(db)
return {
key: destKey,
url: db.items.constructEntryUrl(destKey)
Expand All @@ -62,11 +69,16 @@ export default async function (db, caller, args) {
try {
const destItemEntry = await db.items.get(destKey)
if (destItemEntry) {
// add to existing
destItemEntry.qty += args.qty
await db.items.put(destItemEntry.key, destItemEntry.value)
} else {
// new entry
destValue.qty = args.qty
await db.items.put(destKey, destValue)
if (destValue.owner.dbUrl === db.url) {
await addOwnedItemsIdx(db, destKey, db.items.constructEntryUrl(destKey))
}
}
} finally {
release2()
Expand All @@ -78,8 +90,12 @@ export default async function (db, caller, args) {
await db.items.put(sourceItemEntry.key, sourceItemEntry.value)
} else {
await db.items.del(sourceItemEntry.key)
if (sourceItemEntry.value.owner.dbUrl === db.url) {
await delOwnedItemsIdx(db, destKey)
}
}

await onDatabaseChange(db)
return {
key: destKey,
url: db.items.constructEntryUrl(destKey)
Expand Down
10 changes: 9 additions & 1 deletion schemas/owned-items-idx.json
Expand Up @@ -3,19 +3,27 @@
"title": "Owned Items Index",
"description": "An index of items owned by a user in other databases.",
"type": "json-table",
"keyTemplate": [
{"type": "json-pointer", "value": "/item/userId"},
{"type": "string", "value": ":"},
{"type": "json-pointer", "value": "/item/key"}
],
"definition": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["item", "createdAt"],
"properties": {
"item": {
"type": "object",
"required": ["dbUrl", "userId"],
"required": ["dbUrl", "key", "userId"],
"properties": {
"dbUrl": {
"type": "string",
"format": "uri"
},
"key": {
"type": "string"
},
"userId": {
"type": "string",
"pattern": ".+@.+"
Expand Down
79 changes: 77 additions & 2 deletions tests/items.js
Expand Up @@ -764,6 +764,81 @@ test('managing item classes', async t => {
}))
})

test.skip('inventory view', async t => {
// TODO
test('inventory view', async t => {
let sim = new TestFramework()
let inst = await createServer()
instances = [inst]
let api = inst.api

await sim.createCitizen(inst, 'alice')
await sim.createCitizen(inst, 'bob')
await sim.createCitizen(inst, 'carla')
await sim.users.alice.login()
await sim.createCommunity(inst, 'folks')

const {alice, bob, carla, folks} = sim.users
await bob.login()
await api.communities.join(folks.userId)
await carla.login()
await api.communities.join(folks.userId)

await alice.login()
await sim.dbmethod(inst, folks.userId, 'ctzn.network/put-item-class-method', {
classId: 'paulbucks',
keyTemplate: [
{type: 'json-pointer', value: '/owner/userId'}
]
})
const folksBucks = await sim.dbmethod(inst, folks.userId, 'ctzn.network/create-item-method', {
classId: 'paulbucks',
qty: 100
})
const bobBucks = await sim.dbmethod(inst, folks.userId, 'ctzn.network/transfer-item-method', {
itemKey: folksBucks.result.details.key,
qty: 10,
recp: {userId: bob.userId, dbUrl: bob.dbUrl}
})
const carlaBucks = await sim.dbmethod(inst, folks.userId, 'ctzn.network/transfer-item-method', {
itemKey: bobBucks.result.details.key,
qty: 2,
recp: {userId: carla.userId, dbUrl: carla.dbUrl}
})
const idx1 = (await api.table.list(folks.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx1.length, 1)
t.is(idx1[0].value.item.key, folksBucks.result.details.key)
t.is(idx1[0].value.item.dbUrl, folksBucks.result.details.url)
t.is(idx1[0].value.item.userId, folks.userId)
const idx2 = (await api.table.list(bob.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx2.length, 1)
t.is(idx2[0].value.item.key, bobBucks.result.details.key)
t.is(idx2[0].value.item.dbUrl, bobBucks.result.details.url)
t.is(idx2[0].value.item.userId, folks.userId)
const idx3 = (await api.table.list(carla.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx3.length, 1)
t.is(idx3[0].value.item.key, carlaBucks.result.details.key)
t.is(idx3[0].value.item.dbUrl, carlaBucks.result.details.url)
t.is(idx3[0].value.item.userId, folks.userId)

const aliceBucks = await sim.dbmethod(inst, folks.userId, 'ctzn.network/transfer-item-method', {
itemKey: carlaBucks.result.details.key,
qty: 2,
recp: {userId: alice.userId, dbUrl: alice.dbUrl}
})
const idx4 = (await api.table.list(folks.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx4.length, 1)
t.is(idx4[0].value.item.key, folksBucks.result.details.key)
t.is(idx4[0].value.item.dbUrl, folksBucks.result.details.url)
t.is(idx4[0].value.item.userId, folks.userId)
const idx5 = (await api.table.list(bob.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx5.length, 1)
t.is(idx5[0].value.item.key, bobBucks.result.details.key)
t.is(idx5[0].value.item.dbUrl, bobBucks.result.details.url)
t.is(idx5[0].value.item.userId, folks.userId)
const idx6 = (await api.table.list(carla.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx6.length, 0)
const idx7 = (await api.table.list(alice.userId, 'ctzn.network/owned-items-idx')).entries
t.is(idx7.length, 1)
t.is(idx7[0].value.item.key, aliceBucks.result.details.key)
t.is(idx7[0].value.item.dbUrl, aliceBucks.result.details.url)
t.is(idx7[0].value.item.userId, folks.userId)
})

0 comments on commit b02b9cc

Please sign in to comment.