diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91a3c67c..c28dd6b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@v2 with: repository: CirclesUBI/circles-docker.git - ref: main + ref: update-users-feature path: circles-docker - name: Setup docker repo diff --git a/package-lock.json b/package-lock.json index bb8a2f96..498af99f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@circles/core", - "version": "2.11.2", + "version": "2.12.0-pre-e27388a", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3fcd6e9f..51c2e18b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@circles/core", - "version": "2.11.2", + "version": "2.12.0-pre-e27388a", "description": "Common methods to interact with the Circles ecosystem", "main": "lib/index.js", "contributors": [ diff --git a/src/user.js b/src/user.js index 79319c5e..91bcf765 100644 --- a/src/user.js +++ b/src/user.js @@ -126,6 +126,66 @@ export default function createUserModule(web3, contracts, utils) { return true; }, + /** + * Update username, email address, and/or image url, connected (or not) to a deployed Safe address. + * + * @namespace core.user.update + * + * @param {Object} account - web3 account instance + * @param {Object} userOptions - options + * @param {string} userOptions.safeAddress - owned Safe address + * @param {string} userOptions.username - alphanumerical username + * @param {string} userOptions.email - email address + * @param {string} userOptions.avatarUrl - url of the avatar image + * + * @return {boolean} - Returns true when successful + */ + update: async (account, userOptions) => { + checkAccount(web3, account); + + const options = checkOptions(userOptions, { + safeAddress: { + type: web3.utils.checkAddressChecksum, + }, + username: { + type: 'string', + default: '', + }, + email: { + type: 'string', + default: '', + }, + avatarUrl: { + type: 'string', + default: '', + }, + }); + + const { address } = account; + const { safeAddress, username, email, avatarUrl } = options; + const { signature } = web3.eth.accounts.sign( + [address, safeAddress, username].join(''), + account.privateKey, + ); + + await utils.requestAPI({ + path: ['users', safeAddress], + method: 'POST', + data: { + address: account.address, + signature, + data: { + safeAddress, + username, + email, + avatarUrl, + }, + }, + }); + + return true; + }, + /** * Find multiple user entries by Safe address and username. * @@ -194,5 +254,58 @@ export default function createUserModule(web3, contracts, utils) { }, }); }, + + /** + * Get email of user. + * + * @namespace core.user.getEmail + * + * @param {Object} account - web3 account instance + * @param {Object} userOptions - options + * @param {string} userOptions.safeAddress - owned Safe address + * @param {string} userOptions.username - alphanumerical username + * + * @return {email} - Email of the user + */ + getEmail: async (account, userOptions) => { + checkAccount(web3, account); + + const options = checkOptions(userOptions, { + safeAddress: { + type: web3.utils.checkAddressChecksum, + }, + }); + + const { address } = account; + const { safeAddress } = options; + const { signature } = web3.eth.accounts.sign( + [address, safeAddress].join(''), + account.privateKey, + ); + + try { + const response = await utils.requestAPI({ + path: ['users', safeAddress, 'email'], + method: 'POST', + data: { + address: account.address, + signature, + }, + }); + + if (response && response.data && response.data.email) { + return response.data.email; + } + } catch (error) { + // Do nothing when not found or denied access ... + if ( + !error.request || + (error.request.status !== 404 && error.request.status !== 403) + ) { + throw error; + } + } + return null; + }, }; } diff --git a/test/user.test.js b/test/user.test.js index 02204218..27d5e0eb 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1,5 +1,6 @@ import createCore from './helpers/core'; import getAccount from './helpers/account'; +import { deploySafeAndToken } from './helpers/transactions'; let account; let core; @@ -7,14 +8,16 @@ let safeAddress; let safeCreationNonce; let username; let email; +let deployedSafe; beforeAll(async () => { - account = getAccount(); core = createCore(); }); -describe('User', () => { +describe('User - register', () => { beforeAll(async () => { + account = getAccount(); + safeCreationNonce = new Date().getTime(); safeAddress = await core.safe.prepareDeploy(account, { @@ -60,3 +63,53 @@ describe('User', () => { }); }); }); + +describe('User - update', () => { + beforeAll(async () => { + account = getAccount(1); + + safeCreationNonce = new Date().getTime(); + + // The Safe must be deployed and signedup to the Hub before trying to change the username + deployedSafe = await deploySafeAndToken(core, account); + safeAddress = deployedSafe.safeAddress; + username = `doggy${new Date().getTime()}`; + email = 'dogg@yyy.com'; + }); + + describe('when a new user registers its Safe address', () => { + it('should be resolveable after changing only username', async () => { + expect( + // This update acts as a register + await core.user.update(account, { + email, + safeAddress, + username, + }), + ).toBe(true); + + const newUsername = `dolfin${new Date().getTime()}`; + expect( + await core.user.update(account, { + safeAddress, + username: newUsername, + email, + }), + ).toBe(true); + + const first = await core.user.resolve(account, { + usernames: [newUsername], + }); + + expect(first.data[0].username).toEqual(newUsername); + }); + + it('should return email', async () => { + expect( + await core.user.getEmail(account, { + safeAddress, + }), + ).toBe(email); + }); + }); +});