Commit 37f5d29e authored by mix irving's avatar mix irving
Browse files

BREAKING: change relationship/child > link/profile-profile/child

parent bf22648d
......@@ -29,7 +29,7 @@ const opts = {
]
}
server.whakapapa.child.create(details, opts, (err, relationshipId) => {
server.whakapapa.child.create(details, opts, (err, linkId) => {
// ...
})
```
......@@ -39,16 +39,18 @@ server.whakapapa.child.create(details, opts, (err, relationshipId) => {
server.whakapapa.get('%P5r79KsntEGPMp0Zd24bvfDBMtfx1SADMEE7yxBdVVw=.sha256', (err, data) => {
console.log(data)
// => {
// profileId: '%P5r79KsntEGPMp0Zd24bvfDBMtfx1SADMEE7yxBdVVw=.sha256',
// parents: [
// id: '%P5r79KsntEGPMp0Zd24bvfDBMtfx1SADMEE7yxBdVVw=.sha256',
// parentLinks: [
// {
// profileId: '%4BmROhXcW6H4VyATrIj7uOWqHyW5q5jFZpDHo5RofSc=.sha256',
// relationshipId: '%W5q4BmROhXcW6H4VyATrIj7uOWqHy5jFZpDHo5RofSc=.sha256',
// id: '%4BmROhXcW6H4VyATrIj7uOWqHyW5q5jFZpDHo5RofSc=.sha256',
// linkId: '%W5q4BmROhXcW6H4VyATrIj7uOWqHy5jFZpDHo5RofSc=.sha256',
//
// relationshipType: 'birth',
// legallyAdopted: null // null means it hasnt been set
// tombstone: null
// }
// ],
// children: []
// childLinks: []
// }
})
```
......@@ -83,26 +85,31 @@ Get children and parents of a particular profile.
**`data` format**
```js
{
parents: [
parentLinks: [
{
profileId: '%4BmROhXcW6H4VyATrIj7uOWqHyW5q5jFZpDHo5RofSc=.sha256',
relationshipId: '%47uOWqHyW5q5jFZpDHo5RofBmROhXcW6H4VyATrIjSc=.sha256',
id: '%4BmROhXcW6H4VyATrIj7uOWqHyW5q5jFZpDHo5RofSc=.sha256',
linkId: '%47uOWqHyW5q5jFZpDHo5RofBmROhXcW6H4VyATrIjSc=.sha256',
relationshipType: 'birth',
legallyAdopted: null
legallyAdopted: null,
tombstone: null
},
{
profileId: '%r86qOq2KNY3GlifR6UgQaB05ZfXmHlnxxoFlyi9OI0A=.sha256'
relationshipId: '%W5q5j47uOWqHyFZpDHo5RofBmROhXcW6H4VyATrIjSc=.sha256',
id: '%r86qOq2KNY3GlifR6UgQaB05ZfXmHlnxxoFlyi9OI0A=.sha256'
linkId: '%W5q5j47uOWqHyFZpDHo5RofBmROhXcW6H4VyATrIjSc=.sha256',
relationshipType: null, // this means relationshipType hasnt been set
legallyAdopted: null
legallyAdopted: null,
tombstone: {
date:
}
}
],
children: [
childLinks: [
{
profileId: '%pCt2QB3ZxZEsKIYvxGI9QBmytrUNZfuuRbNk8i0V7ug=.sha256',
relationshipId: '%yFZpDHo5RoW5q5j47uOWqHfBmROhXcW6H4VyATrIjSc=.sha256',
id: '%pCt2QB3ZxZEsKIYvxGI9QBmytrUNZfuuRbNk8i0V7ug=.sha256',
linkId: '%yFZpDHo5RoW5q5j47uOWqHfBmROhXcW6H4VyATrIjSc=.sha256',
relationshipType: 'whangai',
legallyAdopted: false
legallyAdopted: false,
tombstone: null
}
]
}
......@@ -116,29 +123,31 @@ Creates a new updateable message of type `whakapapa/child` connecting two profil
- `child` *String* a scuttlebutt MessageId pointing to a profile root
- `parent` *String* a scuttlebutt MessageId pointing to a profile root
- `opts` *Object* - describes the values to set on that relationship and who can read it.
- `cb` *Function* - a callback of signature `(err, relationshipId)` where `relationshipId` is the id of the relationship which has just been created
- `cb` *Function* - a callback of signature `(err, linkId)` where `linkId` is the id of the relationship which has just been created
**`opts` format**
```js
{
relationshipType: { set: ParentType }, // optional
legallyAdopted: { set: Boolean } // optional
tombstone: { set: TombStone } // optional
recps: [ FeedId ] // optional
}
```
- `MessageId` is scuttlebutt message cypherlink e.g. `%4BmROhXcW6H4VyATrIj7uOWqHyW5q5jFZpDHo5RofSc=.sha256`
- `relationshipType` is any of `'birth' | 'adopted' | 'whangai' | 'unknown'`
- `ParentType` is any of `'birth' | 'adopted' | 'whangai' | 'unknown'`
- `legallyAdopted` should only be set if `relationshipType` is `'adopted'` or `'whangai'`
- `Tombstone` is an Object of form `{ date: UnixTime, reason: String }` and marks a link as deleted.
- `recps` is an Array of `FeedId` (e.g. `'@ye+QM09iPcDJD6YvQYjoQc7sLF/IFhmNbEqgdzQo3lQ=.ed25519'`). If this is provided this relationship will automatically be encrypted so only the mentioned parties will be able to see this. (NOTE - you cannot change this later)
Notes
If you don't want to add any optional opts, set `opts` to `null` or `{}`
- If you don't want to add any optional opts, set `opts` to `null` or `{}`
- this is now a wrapper over `whakapapa.link.create`
:construction: TODO
- add `GroupId` to recps once we have private-groups released
- add `tombstone` property (perhaps a simple-set of people who have tombstoned it) to enable deletion
- add some way to validate whakapapa links
- e.g. an approval system so that kaitiaki (custodians) at each end of the relationship can approve the link?
- need to make sure that can't change pointer for child / parent after an "approval" of link
......@@ -148,8 +157,8 @@ If you don't want to add any optional opts, set `opts` to `null` or `{}`
This updates a **child relationship** (not the child themself).
`server.whakapapa.child.update(relationshipId, opts, cb)`
- `relationshipId` *MessageId* - the root key of the relationship
`server.whakapapa.child.update(linkId, opts, cb)`
- `linkId` *MessageId* - the root key of the relationship
- `opts` *Object* - same as in `child.create`, except recps is set for you based on what the first message was.
- `cb` *function* - callback with signature `(err)`
......@@ -187,13 +196,13 @@ The expected form of `details` is :
```js
{
name: { set: String },
description: { set: String }, // optional
description: { set: String }, // optional
image: { set: Image }, // optional
focus: { set: ProfileId },
mode: { set: Mode },
image: { set: Image }, // optional
recps: [ FeedId, ... ], // optional
tombstone: { set: Tombstone } // optional
ignoredProfiles: { add: [ProfileId] } // optional
tombstone: { set: Tombstone } // optional
ignoredProfiles: { add: [ProfileId] }, // optional
recps: [ FeedId, ... ], // optional
}
```
......@@ -203,7 +212,6 @@ Notes:
- `recps` is a special field which encrypts the view (and all subsequent updates) to a specified set of FeedIds
- `Tombstone` is an Object of form `{ reason: String, date: UnixTime }`
TODO - tombstone
### server.whakapapa.view.update
......@@ -214,6 +222,7 @@ TODO - tombstone
- `recps` is inherrited from the create message (root), so setting it here does nothing.
- `cb` *function* - callback with signature `(err)`
### server.whakapapa.link.create
`server.whakapapa.link.create({ type, parent, child }, details, cb)`
......@@ -226,6 +235,7 @@ TODO - tombstone
- :warning: TODO - add detail here
- `cb`
### server.whakapapa.link.getLinksOfType
`server.whakapapa.link.getLinksOfType(id, type, cb)`
......@@ -233,6 +243,7 @@ TODO - tombstone
- `type` *String* - the "type" of links you want to surface (e.g `link/story-artefact`)
- `cb`
### server.whakapapa.link.update
`server.whakapapa.link.update(id, details, cb)`
......
......@@ -30,7 +30,7 @@ module.exports = {
init: (server) => {
const linkCreate = LinkCreate(server)
const getLinksOfType = GetLinksOfType(server)
const childLinkType = 'relationship/child'
const childLinkType = 'link/profile-profile/child'
return {
link: {
......
const linkSpec = require('../spec/link/story')
const childSpec = require('../spec/child')
const childSpec = require('../spec/link/child')
const storyRegex = /^link\/story-/
const childRegex = /^relationship\/child$/
const storyRegex = /^link\/story-\w+/
const childRegex = /^link\/profile-profile\/child$/
module.exports = function getTypeSpec (type) {
switch (true) {
......
......@@ -26,7 +26,7 @@ module.exports = function PullLinks (server) {
}
}
}]
// this is a query to find all the relationship/child root messages
// this is a query to find all the link/profile-profile/child root messages
// the profileId referenced could be either a parent or child
return pull(
......
......@@ -28,14 +28,10 @@ module.exports = function GetLinksOfType (server) {
return { linkId, state: states[0].state }
}),
pull.drain(
typeSpec.tangle === 'relationship' ? relationshipSink : linkSink,
linkSink,
(err) => { // onErr / on Done
if (err) return cb(err)
if (typeSpec.tangle === 'relationship') {
return cb(null, { profileId: id, parents: result.parentLinks, children: result.childLinks })
}
cb(null, result)
}
)
......@@ -49,14 +45,5 @@ module.exports = function GetLinksOfType (server) {
result.parentLinks.push({ id: state.parent, linkId, ...attrs })
}
}
function relationshipSink ({ linkId, state }) {
const attrs = pick(state, typeSpec.permittedAttrs)
if (state.parent === id) {
result.childLinks.push({ profileId: state.child, relationshipId: linkId, ...attrs })
} else if (state.child === id) {
result.parentLinks.push({ profileId: state.parent, relationshipId: linkId, ...attrs })
}
}
}
}
const Definitions = require('ssb-schema-definitions')
module.exports = Object.assign(
{},
Definitions(),
{
relationshipType: {
type: 'object',
required: ['set'],
properties: {
set: {
oneOf: [
{ type: 'string', pattern: '^birth$' },
{ type: 'string', pattern: '^whangai$' },
{ type: 'string', pattern: '^adopted$' },
{ type: 'string', pattern: '^unknown$' }
]
}
}
},
legallyAdopted: {
type: 'object',
required: ['set'],
properties: {
set: { type: 'boolean' }
}
}
}
)
module.exports = {
type: {
type: 'string',
pattern: '^link/profile-profile/child$'
},
relationshipType: setVal({
oneOf: [
{ type: 'string', pattern: '^birth$' },
{ type: 'string', pattern: '^whangai$' },
{ type: 'string', pattern: '^adopted$' },
{ type: 'string', pattern: '^unknown$' }
]
}),
legallyAdopted: setVal({ type: 'boolean' }),
tombstone: setVal({ $ref: '#/definitions/tombstone' }),
recps: { $ref: '#/definitions/recps' }
}
function setVal (schema) {
return {
type: 'object',
required: ['set'],
properties: {
set: schema
}
}
}
const definitions = require('./definitions')
const properties = require('./properties')
const Definitions = require('ssb-schema-definitions')
module.exports = {
$schema: 'http://json-schema.org/schema#',
type: 'object',
required: ['type', 'child', 'parent', 'tangles'],
properties: {
type: {
type: 'string',
pattern: '^relationship/child$'
},
...properties,
child: { $ref: '#/definitions/messageId' },
parent: { $ref: '#/definitions/messageId' },
relationshipType: { $ref: '#/definitions/relationshipType' },
legallyAdopted: { $ref: '#/definitions/legallyAdopted' },
recps: { $ref: '#/definitions/recps' },
tangles: {
type: 'object',
required: ['relationship'],
......@@ -26,5 +20,5 @@ module.exports = {
}
},
definitions
definitions: Definitions()
}
const definitions = require('./definitions')
const properties = require('./properties')
const Definitions = require('ssb-schema-definitions')
module.exports = {
$schema: 'http://json-schema.org/schema#',
type: 'object',
required: ['type', 'tangles'],
properties: {
type: {
type: 'string',
pattern: '^relationship/child$'
},
...properties,
child: { type: 'null' }, // "child field expected to be empty"
parent: { type: 'null' },
relationshipType: { $ref: '#/definitions/relationshipType' },
legallyAdopted: { $ref: '#/definitions/legallyAdopted' },
recps: { $ref: '#/definitions/recps' },
tangles: {
type: 'object',
required: ['relationship'],
......@@ -26,5 +20,5 @@ module.exports = {
}
},
definitions
definitions: Definitions()
}
......@@ -3,7 +3,6 @@ const Overwrite = require('ssb-tangle/strategy/overwrite')
module.exports = Compose({
relationshipType: Overwrite(),
legallyAdopted: Overwrite()
// TODO tombstone
legallyAdopted: Overwrite(),
tombstone: Overwrite()
})
......@@ -21,7 +21,7 @@ test('child/create (success)', t => {
t.true(isMsg(id), 'calls back with a messageId for relationship')
const expected = {
type: 'relationship/child',
type: 'link/profile-profile/child',
parent,
child,
......
......@@ -39,13 +39,13 @@ test('get', t => {
}),
pull.collect((_, ids) => {
const expected = {
profileId: mix,
parents: [
{ profileId: barbie, relationshipId: ids[0], relationshipType: 'whangai', legallyAdopted: null },
{ profileId: bob, relationshipId: ids[1], relationshipType: null, legallyAdopted: null }
id: mix,
parentLinks: [
{ id: barbie, linkId: ids[0], relationshipType: 'whangai', legallyAdopted: null, tombstone: null },
{ id: bob, linkId: ids[1], relationshipType: null, legallyAdopted: null, tombstone: null }
],
children: [
{ profileId: ziva, relationshipId: ids[2], relationshipType: 'birth', legallyAdopted: null }
childLinks: [
{ id: ziva, linkId: ids[2], relationshipType: 'birth', legallyAdopted: null, tombstone: null }
]
}
normalise(expected)
......@@ -61,13 +61,13 @@ test('get', t => {
server.whakapapa.child.update(ids[0], update, (_) => {
const expected = {
profileId: mix,
parents: [
{ profileId: barbie, relationshipId: ids[0], relationshipType: 'whangai', legallyAdopted: true },
{ profileId: bob, relationshipId: ids[1], relationshipType: null, legallyAdopted: null }
id: mix,
parentLinks: [
{ id: barbie, linkId: ids[0], relationshipType: 'whangai', legallyAdopted: true, tombstone: null },
{ id: bob, linkId: ids[1], relationshipType: null, legallyAdopted: null, tombstone: null }
],
children: [
{ profileId: ziva, relationshipId: ids[2], relationshipType: 'birth', legallyAdopted: null }
childLinks: [
{ id: ziva, linkId: ids[2], relationshipType: 'birth', legallyAdopted: null, tombstone: null }
]
}
normalise(expected)
......@@ -85,5 +85,5 @@ test('get', t => {
})
function normalise (result) {
result.parents = result.parents.sort((a, b) => a - b)
result.parentLinks = result.parentLinks.sort((a, b) => a - b)
}
const test = require('tape')
const isValid = require('../../../../spec/child').isRoot
const isValid = require('../../../../../spec/link/child').isRoot
function mock (override = {}) {
const base = {
type: 'relationship/child',
type: 'link/profile-profile/child',
parent: '%aqH4m8lljjIn2UkDwndSuf93EMK7AvU00kQt/3qxehc=.sha256',
child: '%bazdatAeXPu81tBueITsLYss0vLUdVA7VQa55P21qkc=.sha256',
relationshipType: { set: 'whangai' },
legallyAdopted: { set: true },
tombstone: {
set: {
date: Date.now(),
reason: 'woops'
}
},
recps: [
'@2RNGJafZtMHzd76gyvqH6EJiK7kBnXeak5mBWzoO/iU=.ed25519'
......@@ -38,9 +44,9 @@ test('isRelationshipChild (create)', t => {
// other fields
/// type
isValid(mock({
type: 'iwi' // << expect 'relationship/child'
type: 'iwi' // << expect 'link/profile-profile/child'
}))
t.notEqual(isValid.errors, null, 'type must be "relationship/child"')
t.notEqual(isValid.errors, null, 'type must be "link/profile-profile/child"')
/// parent
isValid(mock({
......
const test = require('tape')
const isValid = require('../../../../spec/child').isUpdate
const isValid = require('../../../../../spec/link/child').isUpdate
/// UPDATE (a mutation)
function mock (override = {}) {
const base = {
type: 'relationship/child',
type: 'link/profile-profile/child',
// NOTE no parent/ child
// this is only set once at create.
......@@ -44,9 +44,9 @@ test('isRelationshipChild (update)', t => {
// other fields
/// type
isValid(mock({
type: 'iwi' // << expect 'relationship/child'
type: 'iwi' // << expect 'link/profile-profile/child'
}))
t.notEqual(isValid.errors, null, 'type must be "relationship/child"')
t.notEqual(isValid.errors, null, 'type must be "link/profile-profile/child"')
/// parent
isValid(mock({
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment