Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/command/access/grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class Grant extends AccessCommand implements LeafCommand {
granteeListRef: response.ref.toHex(),
operation: AccessHistoryOperation.Grant,
createdAt: Date.now(),
grantees: this.grantees,
})

if (this.verbose) {
Expand Down
57 changes: 57 additions & 0 deletions src/command/access/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { LeafCommand, Option } from 'furious-commander'
import { exit } from 'process'
import { AccessHistory } from '../../service/access'
import { AccessHistoryOperation } from '../../service/access/types/history-event'
import { createKeyValue, errorText, formatDate } from '../../utils/text'
import { AccessCommand } from './access-command'

export class History extends AccessCommand implements LeafCommand {
public readonly name = 'history'

public readonly description = 'Show the local history of operations on a grantee list'

@Option({
key: 'list-name',
alias: 'n',
description: 'Name of the grantee list',
required: true,
type: 'string',
})
public listName!: string

public run() {
super.init()

const accessHistory = new AccessHistory(this.commandConfig, this.console)
const events = accessHistory.getEvents(this.listName).sort((a, b) => b.createdAt - a.createdAt)

if (events.length === 0) {
this.console.error(errorText(`Grantee list with name '${this.listName}' does not exist!`))

exit(1)
}

const lastEvent = events[0]
this.console.log(createKeyValue('Latest history address', lastEvent.historyAddress))
this.console.log(createKeyValue('Latest grantee list reference', lastEvent.granteeListRef))

this.console.log('')

for (const event of events) {
this.console.log(createKeyValue('Operation', event.operation))
this.console.log(createKeyValue('Date', formatDate(new Date(event.createdAt))))

if ([AccessHistoryOperation.Grant, AccessHistoryOperation.Revoke].includes(event.operation) && event.grantees) {
this.console.log(createKeyValue('Grantees', event.grantees.join(', ')))
}

if (this.verbose) {
this.console.log(createKeyValue('Stamp', event.stampId))
this.console.log(createKeyValue('Grantee list reference', event.granteeListRef))
this.console.log(createKeyValue('History address', event.historyAddress))
}

this.console.log('---')
}
}
}
3 changes: 2 additions & 1 deletion src/command/access/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GroupCommand } from 'furious-commander'
import { Grant } from './grant'
import { History } from './history'
import { Init } from './init'
import { List } from './list'
import { Revoke } from './revoke'
Expand All @@ -9,5 +10,5 @@ export class Access implements GroupCommand {

public readonly description = 'Share access to your uploaded files/folders'

public subCommandClasses = [Init, Grant, Revoke, List]
public subCommandClasses = [Init, Grant, Revoke, List, History]
}
3 changes: 2 additions & 1 deletion src/command/access/revoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class Revoke extends AccessCommand implements LeafCommand {
const response = await this.bee.patchGrantees(stampId, granteeListRef, historyAddress, { revoke: this.grantees })

if (response.status === 200) {
this.console.log(successText(`Access revoked from ${this.grantees.join(', ')}!`))
this.console.log(successText(`Access revoked from ${this.grantees.join(', ')}`))
}

accessHistory.addEvent(this.listName, {
Expand All @@ -60,6 +60,7 @@ export class Revoke extends AccessCommand implements LeafCommand {
granteeListRef: response.ref.toHex(),
operation: AccessHistoryOperation.Revoke,
createdAt: Date.now(),
grantees: this.grantees,
})

if (this.verbose) {
Expand Down
9 changes: 7 additions & 2 deletions src/service/access/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,18 @@ export class AccessHistory {
history[granteeListName] = []
}

history[granteeListName].push({
const newEvent = {
stampId: event.stampId,
historyAddress: event.historyAddress,
granteeListRef: event.granteeListRef,
operation: event.operation,
createdAt: event.createdAt,
})
} as AccessHistoryEvent

if (event.grantees) {
newEvent.grantees = event.grantees
}
history[granteeListName].push(event)

writeFileSync(this.commandConfig.getAccessHistoryFilePath(), JSON.stringify(history))
}
Expand Down
1 change: 1 addition & 0 deletions src/service/access/types/history-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type AccessHistoryEvent = {
granteeListRef: string
operation: AccessHistoryOperation
createdAt: number
grantees?: string[]
}

export type AccessHistoryLog = { [name: string]: AccessHistoryEvent[] }
4 changes: 2 additions & 2 deletions src/service/stamp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Bee, PostageBatch } from '@ethersphere/bee-js'
import { Dates } from 'cafe-utility'
import { exit } from 'process'
import { CommandLog } from '../../command/root-command/command-log'
import { createKeyValue } from '../../utils/text'
import { createKeyValue, formatDate } from '../../utils/text'

/**
* Displays an interactive stamp picker to select a Stamp ID.
Expand Down Expand Up @@ -60,7 +60,7 @@ export function printStamp(stamp: PostageBatch, console: CommandLog, settings?:

if (settings?.showTtl) {
const ttl = Dates.secondsToHumanTime(stamp.duration.toSeconds())
const expires = stamp.duration.toEndDate().toISOString().replace('T', ' ').slice(0, 19) + ' UTC'
const expires = formatDate(stamp.duration.toEndDate())
console.log(createKeyValue('TTL', `${ttl} (${expires})`))
}

Expand Down
4 changes: 4 additions & 0 deletions src/utils/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export function ellipsis(value: string, startIndex: number, endIndex?: number |
return `${value.slice(0, startIndex)}...${endIndex ? value.slice(endIndex) : ''}`
}

export function formatDate(date: Date): string {
return date.toISOString().replace('T', ' ').slice(0, 19) + ' UTC'
}

export function printDivided<T>(
items: T[],
printFn: (item: T, console: CommandLog) => void,
Expand Down
38 changes: 36 additions & 2 deletions test/command/access.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describeCommand(
await System.sleepMillis(1000)
const pubKey = await getPublicAddress('http://localhost:21633')
await invokeTestCli(['access', 'grant', '--list-name', 'test-access', '--grantee', pubKey])
expect(getLastMessage()).toContain(`Access granted to ${pubKey}!`)
expect(getLastMessage()).toContain(`Access granted to ${pubKey}`)
})

describe('when grantee list does not exist', () => {
Expand Down Expand Up @@ -88,7 +88,7 @@ describeCommand(
await invokeTestCli(['access', 'init', ...getStampOption(), '-n', 'test-access', '--grantee', pubKey])
await System.sleepMillis(1000)
await invokeTestCli(['access', 'revoke', '--list-name', 'test-access', '--grantee', pubKey])
expect(getLastMessage()).toContain(`Access revoked from ${pubKey}!`)
expect(getLastMessage()).toContain(`Access revoked from ${pubKey}`)
})

describe('when grantee list does not exist', () => {
Expand Down Expand Up @@ -139,6 +139,40 @@ describeCommand(
await invokeTestCli(['access', 'list', '--list-name', 'test-access'])
expect(getLastMessage()).toContain("Grantee list 'test-access' has no grantees.")
})

describe('when grantee list does not exist', () => {
it('should show error message', async () => {
await invokeTestCli(['access', 'list', '-n', 'nonexistent-list'])
expect(consoleMessages[0]).toContain("Grantee list with name 'nonexistent-list' does not exist!")
expect(consoleMessages[1]).toContain('process.exit() was called with code 1')
})
})
})

describe('history', () => {
it('should show the history of operations on a grantee list', async () => {
const granteePubKey = await getPublicAddress('http://localhost:21633')
await invokeTestCli(['access', 'init', ...getStampOption(), '-n', 'test-access', '--grantee', granteePubKey])
await System.sleepMillis(1000)
await invokeTestCli(['access', 'revoke', '--list-name', 'test-access', '--grantee', granteePubKey])
await System.sleepMillis(1000)
await invokeTestCli(['access', 'history', '--list-name', 'test-access'])
process.stderr.write(consoleMessages.join('\n'))
expect(getNthLastMessage(10)).toContain('Latest history address')
expect(getNthLastMessage(9)).toContain('Latest grantee list reference')
expect(getNthLastMessage(7)).toContain('revoke')
expect(getNthLastMessage(5)).toContain('Grantees:')
expect(getNthLastMessage(5)).toContain(granteePubKey)
expect(getNthLastMessage(3)).toContain('init')
})

describe('when grantee list does not exist', () => {
it('should show error message', async () => {
await invokeTestCli(['access', 'history', '-n', 'nonexistent-list'])
expect(consoleMessages[0]).toContain("Grantee list with name 'nonexistent-list' does not exist!")
expect(consoleMessages[1]).toContain('process.exit() was called with code 1')
})
})
})
},
{ configFileName: 'access' },
Expand Down
Loading