Skip to content

Commit

Permalink
feat: Add updateResource endpoint and tests
Browse files Browse the repository at this point in the history
Signed-off-by: Sownak Roy <[email protected]>
  • Loading branch information
sownak committed Dec 10, 2024
1 parent e1fab41 commit eeb33a5
Show file tree
Hide file tree
Showing 10 changed files with 846 additions and 35 deletions.
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ The purpose of this service is to provide a [Universal Registrar driver](https:/

## 📖 Endpoints

- `/create`
- `/update`
- `/deactivate`
- `/create-resource`
- `/api-docs`
- POST `/create`
- POST `/update`
- POST `/deactivate`
- POST `/{did}/create-resource`
- POST `/createResource`
- POST `/updateResource`
- GET `/key-pair`
- GET `/did-document`
- GET `/properties`
- GET `/methods`
- GET `/traits`

## 🧑‍💻🛠 Developer Guide

Expand Down Expand Up @@ -65,6 +71,23 @@ npm run build
npm start
```

### 🛠 Testing

This repository contains the playwright tests for unit and integration testing.
Add any additional tests in the `tests` directory.

You must set up these two env vars before running test:
1. `TEST_PRIVATE_KEY` : Private key for signing the requests
2. `TEST_PUBLIC_KEY` : Corresponding public key

Then execute the tests

```bash
npm run test
# if tests faile because of parallelism, run
npm run test -- --workers=1
```

## 🐞 Bug reports & 🤔 feature requests

If you notice anything not behaving how you expected, or would like to make a suggestion / request for a new feature, please create a [**new issue**](https://github.com/cheqd/did-registrar/issues/new/choose) and let us know.
Expand Down
4 changes: 2 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ class App {
);
app.post(
`${URL_PREFIX}/updateResource`,
ResourceController.createResourceValidator,
ResourceController.updateResourceValidator,
DidController.commonValidator,
new ResourceController().create
new ResourceController().updateResource
);

app.get(`${URL_PREFIX}/methods`, (req: Request, res: Response) => {
Expand Down
181 changes: 177 additions & 4 deletions src/controllers/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { v4 } from 'uuid';
import { fromString } from 'uint8arrays';

import { CheqdRegistrar, CheqdResolver, NetworkType } from '../service/cheqd.js';
import { IResourceCreateRequest, IResourceCreateRequestV1, IResourceUpdateRequest, IState } from '../types/types.js';
import {
ContentOperation,
IResourceCreateRequest,
IResourceCreateRequestV1,
IResourceUpdateRequest,
IState,
} from '../types/types.js';
import { Messages } from '../types/constants.js';
import { convertToSignInfo } from '../helpers/helpers.js';
import { Responses } from '../helpers/response.js';
Expand Down Expand Up @@ -37,14 +43,41 @@ export class ResourceController {
return true;
})
.withMessage('name, type and content are required'),
check('content').exists().isString().withMessage(Messages.Invalid),
check('name').optional().isString().withMessage(Messages.Invalid),
check('type').optional().isString().withMessage(Messages.Invalid),
check('content').exists().isString().withMessage(Messages.Invalid),
check('version').optional().isString().withMessage(Messages.Invalid),
check('relativeDidUrl').optional().isString().contains('/resources/').withMessage(Messages.InvalidDidUrl),
check('alsoKnownAs').optional().isArray().withMessage(Messages.Invalid),
check('alsoKnownAs.*.uri').isString().withMessage(Messages.Invalid),
check('alsoKnownAs.*.description').isString().withMessage(Messages.Invalid),
];
public static updateResourceValidator = [
check('did').exists().isString().contains('did:cheqd').withMessage(Messages.InvalidDid),
check('jobId')
.custom((value, { req }) => {
if (!value && !(req.body.name && req.body.type && req.body.content)) return false;
return true;
})
.withMessage('name, type and content are required'),
check('name').optional().isString().withMessage(Messages.Invalid),
check('type').optional().isString().withMessage(Messages.Invalid),
check('content')
.exists()
.isArray()
.custom((value) => {
if (value.length !== 1) return false;
if (typeof value[0] !== 'string') return false;
return true;
})
.withMessage('The content array must be provided and must have exactly one string'),
check('relativeDidUrl').exists().isString().contains('/resources/').withMessage(Messages.InvalidDidUrl),
check('contentOperation')
.optional()
.isArray()
.custom((value) => value[0] === ContentOperation.Set && value.length == 1)
.withMessage('Only Set operation is supported'),
];

public async create(request: Request, response: Response) {
const result = validationResult(request);
Expand Down Expand Up @@ -183,7 +216,7 @@ export class ResourceController {
if (!resolvedDocument?.didDocument || resolvedDocument.didDocumentMetadata.deactivated) {
return response
.status(400)
.send(Responses.GetInvalidResponse({ id: did }, secret, Messages.DidNotFound));
.send(Responses.GetInvalidResourceResponse(did, {}, secret, Messages.DidNotFound));
} else {
resolvedDocument = resolvedDocument.didDocument;
}
Expand All @@ -197,7 +230,7 @@ export class ResourceController {
return response.status(201).json({
jobId,
didUrlState: {
resourceId: storeData.resource.id,
didUrl: storeData.resource.id,
state: IState.Finished,
secret,
resource: storeData.resource,
Expand Down Expand Up @@ -242,6 +275,146 @@ export class ResourceController {
);
}

options.network = options.network || (did.split(':')[2] as NetworkType);
await CheqdRegistrar.instance.connect(options);
const result = await CheqdRegistrar.instance.createResource(signInputs, resourcePayload);
if (result.code == 0) {
return response
.status(201)
.json(Responses.GetResourceSuccessResponse(jobId, secret, did, resourcePayload));
} else {
return response
.status(400)
.json(Responses.GetInvalidResourceResponse(did, resourcePayload, secret, Messages.InvalidResource));
}
} catch (error) {
return response.status(500).json({
jobId,
didUrlState: {
state: IState.Failed,
reason: Messages.Internal,
description: Messages.TryAgain + error,
secret,
resourcePayload,
},
});
}
}
public async updateResource(request: Request, response: Response) {
const result = validationResult(request);
if (!result.isEmpty()) {
return response
.status(400)
.json(Responses.GetInvalidResourceResponse('', {}, request.body.secret, result.array()[0].msg));
}

let {
did,
jobId,
content,
relativeDidUrl,
name,
type,
version,
secret = {},
options = {},
} = request.body as IResourceUpdateRequest;

let resourcePayload: Partial<MsgCreateResourcePayload> = {};
let existingResource;
try {
// check if did is registered on the ledger
let resolvedDocument = await CheqdResolver(did);
if (!resolvedDocument?.didDocument || resolvedDocument.didDocumentMetadata.deactivated) {
return response
.status(400)
.send(Responses.GetInvalidResourceResponse(did, {}, secret, Messages.DidNotFound));
} else {
const didUrlIndex = resolvedDocument.didDocumentMetadata.linkedResourceMetadata.findIndex(
(resource: { resourceURI: string }) => resource.resourceURI === did + relativeDidUrl
);
if (didUrlIndex !== -1) {
existingResource = resolvedDocument.didDocumentMetadata.linkedResourceMetadata[didUrlIndex];
if (existingResource.resourceName !== name || existingResource.resourceType !== type)
return response
.status(400)
.send(
Responses.GetInvalidResourceResponse(
did,
{ id: relativeDidUrl.split('resources/')[1] },
secret,
Messages.InvalidUpdateResource
)
);
} else {
return response
.status(400)
.send(
Responses.GetInvalidResourceResponse(
did,
{ id: relativeDidUrl.split('resources/')[1] },
secret,
Messages.ResourceNotFound
)
);
}
resolvedDocument = resolvedDocument.didDocument;
}

// Validate and get store data if any
if (jobId) {
const storeData = LocalStore.instance.getResource(jobId);
if (!storeData) {
return response.status(400).json(Responses.GetJobExpiredResponse(jobId));
} else if (storeData.state == IState.Finished) {
return response.status(201).json({
jobId,
didUrlState: {
didUrl: storeData.resource.id,
state: IState.Finished,
secret,
resource: storeData.resource,
},
});
}

resourcePayload = storeData.resource;
resourcePayload.data = new Uint8Array(Object.values(resourcePayload.data!));
} else if (!content) {
return response
.status(400)
.json(Responses.GetInvalidResourceResponse('', {}, secret, Messages.InvalidContent));
} else {
jobId = v4();

resourcePayload = {
collectionId: did.split(':').pop()!,
id: v4(),
name,
resourceType: type,
version: version,
data: fromString(content[0], 'base64'),
};
}

let signInputs: SignInfo[];

if (secret.signingResponse) {
signInputs = convertToSignInfo(secret.signingResponse);
} else {
LocalStore.instance.setResource(jobId, { resource: resourcePayload, state: IState.Action });
return response
.status(200)
.json(
Responses.GetResourceActionSignatureResponse(
jobId,
resolvedDocument.verificationMethod,
did,
resourcePayload
)
);
}

options.network = options.network || (did.split(':')[2] as NetworkType);
await CheqdRegistrar.instance.connect(options);
const result = await CheqdRegistrar.instance.createResource(signInputs, resourcePayload);
Expand Down
2 changes: 2 additions & 0 deletions src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export enum Messages {
InvalidJob = 'The jobId is either expired or not found',
SecretValidation = 'Provide either a valid KeyPair or Signature',
InvalidResource = 'Resource Data is invalid',
ResourceNotFound = 'Resource does not exist',
InvalidContent = 'Resource Content is invalid',
InvalidUpdateResource = 'Update resource name or type does not match existing resource',
TestnetFaucet = 'sketch mountain erode window enact net enrich smoke claim kangaroo another visual write meat latin bacon pulp similar forum guilt father state erase bright',
SigingResponse = 'e.g. { kid: did:cheqd:testnet:qsqdcansoica#key-1, signature: aca1s12q14213casdvaadcfas }',
InvalidOptions = 'The provided options are invalid',
Expand Down
3 changes: 3 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ export interface IResourceUpdateRequest {
relativeDidUrl: string;
content: any;
contentOperation: ContentOperation[];
name: string;
type: string;
version: string;
}

export enum ContentOperation {
Expand Down
Loading

0 comments on commit eeb33a5

Please sign in to comment.