Skip to content

Commit b227a10

Browse files
authored
bucket labels (#1384)
* bucket labels * compact migration * same bucket label if patch undefined * fix review comments
1 parent 59d927f commit b227a10

11 files changed

Lines changed: 488 additions & 16 deletions

File tree

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"stream-concat": "^1.0.0",
101101
"tar": "^7.5.11",
102102
"uint8arrays": "^4.0.6",
103+
"unique-names-generator": "^4.7.1",
103104
"url-join": "^5.0.0",
104105
"winston": "^3.11.0",
105106
"winston-daily-rotate-file": "^4.7.1",

src/@types/commands.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,15 @@ export interface PersistentStorageCreateBucketCommand extends Command {
344344
signature: string
345345
nonce: string
346346
accessLists: AccessList[]
347+
label?: string
348+
}
349+
350+
export interface PersistentStorageUpdateBucketCommand extends Command {
351+
consumerAddress: string
352+
signature: string
353+
nonce: string
354+
bucketId: string
355+
label?: string
347356
}
348357

349358
export interface PersistentStorageGetBucketsCommand extends Command {

src/components/core/handler/coreHandlersRegistry.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
PersistentStorageGetBucketsHandler,
5454
PersistentStorageGetFileObjectHandler,
5555
PersistentStorageListFilesHandler,
56+
PersistentStorageUpdateBucketHandler,
5657
PersistentStorageUploadFileHandler
5758
} from './persistentStorage.js'
5859
import { GetAccessListHandler, SearchAccessListHandler } from './accessListHandler.js'
@@ -181,6 +182,10 @@ export class CoreHandlersRegistry {
181182
PROTOCOL_COMMANDS.PERSISTENT_STORAGE_CREATE_BUCKET,
182183
new PersistentStorageCreateBucketHandler(node)
183184
)
185+
this.registerCoreHandler(
186+
PROTOCOL_COMMANDS.PERSISTENT_STORAGE_UPDATE_BUCKET,
187+
new PersistentStorageUpdateBucketHandler(node)
188+
)
184189
this.registerCoreHandler(
185190
PROTOCOL_COMMANDS.PERSISTENT_STORAGE_GET_BUCKETS,
186191
new PersistentStorageGetBucketsHandler(node)

src/components/core/handler/persistentStorage.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
PersistentStorageGetBucketsCommand,
66
PersistentStorageGetFileObjectCommand,
77
PersistentStorageListFilesCommand,
8+
PersistentStorageUpdateBucketCommand,
89
PersistentStorageUploadFileCommand
910
} from '../../../@types/commands.js'
1011
import {
@@ -23,6 +24,21 @@ import {
2324
} from '../../httpRoutes/validateCommands.js'
2425
import { CommandHandler } from './handler.js'
2526

27+
const MAX_BUCKET_LABEL_LENGTH = 256
28+
29+
function validateOptionalLabel(label: unknown): ValidateParams | null {
30+
if (label === undefined || label === null) return null
31+
if (typeof label !== 'string') {
32+
return buildInvalidRequestMessage('Invalid parameter: "label" must be a string')
33+
}
34+
if (label.length > MAX_BUCKET_LABEL_LENGTH) {
35+
return buildInvalidRequestMessage(
36+
`Invalid parameter: "label" must be at most ${MAX_BUCKET_LABEL_LENGTH} characters`
37+
)
38+
}
39+
return null
40+
}
41+
2642
function requirePersistentStorage(handler: CommandHandler): PersistentStorageFactory {
2743
const node = handler.getOceanNode() as any
2844
if (!node.getPersistentStorage) {
@@ -44,6 +60,8 @@ export class PersistentStorageCreateBucketHandler extends CommandHandler {
4460
'Invalid parameter: "accessLists" must be an array of objects'
4561
)
4662
}
63+
const labelError = validateOptionalLabel(command.label)
64+
if (labelError) return labelError
4765
return { valid: true }
4866
}
4967

@@ -97,7 +115,11 @@ export class PersistentStorageCreateBucketHandler extends CommandHandler {
97115
}
98116
}
99117

100-
const result = await storage.createNewBucket(task.accessLists, ownerNormalized)
118+
const result = await storage.createNewBucket(
119+
task.accessLists,
120+
ownerNormalized,
121+
task.label
122+
)
101123
return {
102124
stream: Readable.from(JSON.stringify(result)),
103125
status: { httpStatus: 200, error: null }
@@ -110,6 +132,59 @@ export class PersistentStorageCreateBucketHandler extends CommandHandler {
110132
}
111133
}
112134

135+
export class PersistentStorageUpdateBucketHandler extends CommandHandler {
136+
validate(command: PersistentStorageUpdateBucketCommand): ValidateParams {
137+
const base = validateCommandParameters(command, ['bucketId'])
138+
if (!base.valid) return base
139+
if (!command.bucketId || typeof command.bucketId !== 'string') {
140+
return buildInvalidRequestMessage('Invalid parameter: "bucketId" must be a string')
141+
}
142+
const labelError = validateOptionalLabel(command.label)
143+
if (labelError) return labelError
144+
return { valid: true }
145+
}
146+
147+
async handle(task: PersistentStorageUpdateBucketCommand): Promise<P2PCommandResponse> {
148+
const validationResponse = await this.verifyParamsAndRateLimits(task)
149+
if (this.shouldDenyTaskHandling(validationResponse)) return validationResponse
150+
151+
const isAuthRequestValid = await this.validateTokenOrSignature(
152+
task.authorization,
153+
task.consumerAddress,
154+
task.nonce,
155+
task.signature,
156+
task.command
157+
)
158+
if (isAuthRequestValid.status.httpStatus !== 200) return isAuthRequestValid
159+
160+
try {
161+
const storage = requirePersistentStorage(this)
162+
const ownerNormalized = task.consumerAddress
163+
? getAddress(task.consumerAddress)
164+
: getAddress(await this.getAddressFromToken(task.authorization))
165+
const label = await storage.updateBucketLabel(
166+
task.bucketId,
167+
task.label,
168+
ownerNormalized
169+
)
170+
return {
171+
stream: Readable.from(JSON.stringify({ bucketId: task.bucketId, label })),
172+
status: { httpStatus: 200, error: null }
173+
}
174+
} catch (e) {
175+
if (e instanceof PersistentStorageAccessDeniedError) {
176+
return { stream: null, status: { httpStatus: 403, error: e.message } }
177+
}
178+
const message = e instanceof Error ? e.message : String(e)
179+
if (message.toLowerCase().includes('not found')) {
180+
return { stream: null, status: { httpStatus: 404, error: message } }
181+
}
182+
CORE_LOGGER.error(`PersistentStorageUpdateBucketHandler error: ${message}`)
183+
return { stream: null, status: { httpStatus: 500, error: message } }
184+
}
185+
}
186+
}
187+
113188
export class PersistentStorageGetBucketsHandler extends CommandHandler {
114189
validate(command: PersistentStorageGetBucketsCommand): ValidateParams {
115190
const base = validateCommandParameters(command, ['owner'])

src/components/httpRoutes/persistentStorage.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
PersistentStorageGetBucketsHandler,
1212
PersistentStorageGetFileObjectHandler,
1313
PersistentStorageListFilesHandler,
14+
PersistentStorageUpdateBucketHandler,
1415
PersistentStorageUploadFileHandler
1516
} from '../core/handler/persistentStorage.js'
1617

@@ -43,6 +44,37 @@ persistentStorageRoutes.post(
4344
}
4445
)
4546

47+
// Update bucket (rename / set label)
48+
persistentStorageRoutes.patch(
49+
`${SERVICES_API_BASE_PATH}/persistentStorage/buckets/:bucketId`,
50+
express.json(),
51+
async (req, res) => {
52+
try {
53+
const response = await new PersistentStorageUpdateBucketHandler(
54+
req.oceanNode
55+
).handle({
56+
command: PROTOCOL_COMMANDS.PERSISTENT_STORAGE_UPDATE_BUCKET,
57+
consumerAddress: req.query.consumerAddress as string,
58+
signature: req.query.signature as string,
59+
nonce: req.query.nonce as string,
60+
bucketId: req.params.bucketId,
61+
label: req.body?.label,
62+
authorization: req.headers?.authorization,
63+
caller: req.caller
64+
} as any)
65+
if (!response.stream) {
66+
res.status(response.status.httpStatus).send(response.status.error)
67+
return
68+
}
69+
const payload = await streamToObject(response.stream as Readable)
70+
res.status(200).json(payload)
71+
} catch (error) {
72+
HTTP_LOGGER.error(`PersistentStorage update bucket error: ${error}`)
73+
res.status(500).send('Internal Server Error')
74+
}
75+
}
76+
)
77+
4678
// List buckets for an owner (then filtered by ACL in handler)
4779
persistentStorageRoutes.get(
4880
`${SERVICES_API_BASE_PATH}/persistentStorage/buckets`,

0 commit comments

Comments
 (0)