Skip to content

Commit 41c85ec

Browse files
authored
Merge pull request #512 from Kpoke/keycloak
create a new parent wallet
2 parents 064ec8c + 68a9fce commit 41c85ec

File tree

14 files changed

+273
-24
lines changed

14 files changed

+273
-24
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## [1.42.2](https://github.com/Greenstand/treetracker-wallet-api/compare/v1.42.1...v1.42.2) (2025-04-17)
2+
13
## [1.43.1-keycloak.2](https://github.com/Greenstand/treetracker-wallet-api/compare/v1.43.1-keycloak.1...v1.43.1-keycloak.2) (2025-04-15)
24

35

@@ -6,6 +8,9 @@
68
* env variable ([144a446](https://github.com/Greenstand/treetracker-wallet-api/commit/144a4469ee99fef5a2252fa2d8b8eb531ea7120a))
79

810
## [1.43.1-keycloak.1](https://github.com/Greenstand/treetracker-wallet-api/compare/v1.43.0...v1.43.1-keycloak.1) (2025-04-15)
11+
* s3 url ([c642005](https://github.com/Greenstand/treetracker-wallet-api/commit/c6420057616afcf77f3c99ce812e7791a2fef10d))
12+
13+
## [1.42.1](https://github.com/Greenstand/treetracker-wallet-api/compare/v1.42.0...v1.42.1) (2025-04-17)
914

1015

1116
### Bug Fixes
@@ -18,6 +23,9 @@
1823
### Bug Fixes
1924

2025
* fix integration tests ([56217c1](https://github.com/Greenstand/treetracker-wallet-api/commit/56217c1c43eaa976bd1e718cedd553cb7e42ea91))
26+
* s3 url ([5b4aa80](https://github.com/Greenstand/treetracker-wallet-api/commit/5b4aa804ff112030126c99ac103b45c90d91cbb8))
27+
28+
# [1.42.0](https://github.com/Greenstand/treetracker-wallet-api/compare/v1.41.1...v1.42.0) (2025-02-18)
2129

2230

2331
### Features

__tests__/wallet-get.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ require('dotenv').config();
22
const request = require('supertest');
33
const { expect } = require('chai');
44
const chai = require('chai');
5+
const uuid = require('uuid');
56
const server = require('../server/app');
67
const seed = require('./seed');
78
chai.use(require('chai-uuid'));
@@ -34,6 +35,15 @@ describe('Wallet: Get wallets of an account', () => {
3435
expect(res.count).to.eq(11);
3536
});
3637

38+
it('should return 401, user does not exist', async () => {
39+
await request(server)
40+
.get('/wallets')
41+
.send({ wallet: 'azAZ.-@0123456789' })
42+
.set('content-type', 'application/json')
43+
.set('Authorization', `Bearer ${uuid.v4()}`)
44+
.expect(401);
45+
});
46+
3747
it('Get wallets of WalletA without params', async () => {
3848
const res = await request(server)
3949
.get('/wallets')

__tests__/wallet-post.spec.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ require('dotenv').config();
22
const request = require('supertest');
33
const { expect } = require('chai');
44
const chai = require('chai');
5+
const uuid = require('uuid');
56
const server = require('../server/app');
67
const seed = require('./seed');
78
chai.use(require('chai-uuid'));
89
const WalletService = require('../server/services/WalletService');
10+
const TrustService = require('../server/services/TrustService');
911

1012
describe('Wallet: create(POST) wallets of an account', () => {
1113
let bearerTokenA;
@@ -17,19 +19,57 @@ describe('Wallet: create(POST) wallets of an account', () => {
1719
bearerTokenA = seed.wallet.keycloak_account_id;
1820
});
1921

22+
it('create parent wallet', async () => {
23+
const keycloakId = uuid.v4();
24+
const res = await request(server)
25+
.post('/wallets')
26+
.send({ wallet: 'newParentWallet' })
27+
.set('content-type', 'application/json')
28+
.set('Authorization', `Bearer ${keycloakId}`)
29+
.expect(201);
30+
31+
expect(res.body).contain({ wallet: 'newParentWallet' });
32+
expect(res.body.id).to.exist;
33+
34+
const walletService = new WalletService();
35+
const wallet = await walletService.getWalletIdByKeycloakId(keycloakId);
36+
expect(wallet.id).equal(res.body.id);
37+
38+
const trustService = new TrustService();
39+
const trust = await trustService.getTrustRelationships(res.body.id, [], {
40+
walletId: res.body.id,
41+
});
42+
expect(trust.count).equal(0);
43+
expect(trust.result.length).equal(0);
44+
});
45+
2046
it('create wallet by a valid wallet name', async () => {
2147
const walletService = new WalletService();
2248
const res = await request(server)
2349
.post('/wallets')
2450
.send({ wallet: 'azAZ.-@0123456789' })
2551
.set('content-type', 'application/json')
26-
.set('Authorization', `Bearer ${bearerTokenA}`);
52+
.set('Authorization', `Bearer ${bearerTokenA}`)
53+
.expect(201);
2754

2855
expect(res.body).contain({ wallet: 'azAZ.-@0123456789' });
2956
expect(res.body.id).to.exist;
3057
await walletService.getById(res.body.id).then((wallet) => {
3158
expect(wallet.name).to.eq('azAZ.-@0123456789');
3259
});
60+
61+
const trustService = new TrustService();
62+
const trust = await trustService.getTrustRelationships(res.body.id, [], {
63+
walletId: res.body.id,
64+
});
65+
expect(trust.count).equal(1);
66+
expect(trust.result.length).equal(1);
67+
expect(trust.result[0].originating_wallet).equal('walletA');
68+
expect(trust.result[0].actor_wallet).equal('walletA');
69+
expect(trust.result[0].target_wallet).equal('azAZ.-@0123456789');
70+
expect(trust.result[0].type).equal('manage');
71+
expect(trust.result[0].state).equal('trusted');
72+
expect(trust.result[0].request_type).equal('manage');
3373
});
3474

3575
it('create wallet by invalid name length', async () => {

server/handlers/trustHandler/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const {
55
trustGetQuerySchema,
66
} = require('./schemas');
77

8+
// @TODO(deprecated) is this endpoint still in use
9+
// /wallet/:id/trust_relationships works better
10+
// if not in use remove and remove the getAllTrustRelationships function
811
const trustGet = async (req, res) => {
912
const validatedQuery = await trustGetQuerySchema.validateAsync(req.query, {
1013
abortEarly: false,

server/handlers/walletHandler.spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,34 @@ describe('walletRouter', () => {
194194
).eql(true);
195195
});
196196

197+
it('successfully creates parent wallet', async () => {
198+
sinon.restore();
199+
sinon.stub(JWTService, 'verify').returns({
200+
id: keycloakId,
201+
});
202+
sinon
203+
.stub(WalletService.prototype, 'getWalletIdByKeycloakId')
204+
.resolves({});
205+
const createParentWalletStub = sinon
206+
.stub(WalletService.prototype, 'createParentWallet')
207+
.resolves(mockWallet);
208+
const res = await request(app).post('/wallets').send({
209+
wallet: mockWallet.wallet,
210+
about: mockWallet.about,
211+
});
212+
expect(res).property('statusCode').eq(201);
213+
expect(res.body.wallet).eq(mockWallet.wallet);
214+
expect(res.body.id).eq(mockWallet.id);
215+
expect(res.body.about).eq(mockWallet.about);
216+
expect(
217+
createParentWalletStub.calledOnceWithExactly(
218+
keycloakId,
219+
mockWallet.wallet,
220+
mockWallet.about,
221+
),
222+
).eql(true);
223+
});
224+
197225
it('missed parameter', async () => {
198226
const res = await request(app).post('/wallets').send({});
199227
expect(res).property('statusCode').eq(422);

server/handlers/walletHandler/index.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
walletBatchTransferBodySchema,
1515
csvValidationSchemaTransfer,
1616
} = require('./schemas');
17+
const HttpError = require('../../utils/HttpError');
1718

1819
const walletGet = async (req, res) => {
1920
const validatedQuery = await walletGetQuerySchema.validateAsync(req.query, {
@@ -141,11 +142,24 @@ const walletPost = async (req, res) => {
141142
const { wallet_id } = req;
142143
const { wallet: walletToBeCreated, about } = validatedBody;
143144
const walletService = new WalletService();
144-
const returnedWallet = await walletService.createWallet(
145-
wallet_id,
146-
walletToBeCreated,
147-
about,
148-
);
145+
146+
let returnedWallet;
147+
if (!wallet_id) {
148+
// new keycloak user
149+
const { keycloak_id } = req;
150+
if (!keycloak_id) throw new HttpError(500, 'keycloak id not found');
151+
returnedWallet = await walletService.createParentWallet(
152+
keycloak_id,
153+
walletToBeCreated,
154+
about,
155+
);
156+
} else {
157+
returnedWallet = await walletService.createWallet(
158+
wallet_id,
159+
walletToBeCreated,
160+
about,
161+
);
162+
}
149163

150164
res.status(201).json(returnedWallet);
151165
};

server/models/Wallet.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ class Wallet {
1313
this._tokenRepository = new TokenRepository(session);
1414
}
1515

16-
async createWallet(loggedInWalletId, wallet, about) {
17-
// check name
16+
async checkWalletName(wallet) {
1817
try {
1918
await this._walletRepository.getByName(wallet);
2019
throw new HttpError(409, `The wallet "${wallet}" already exists`);
@@ -25,8 +24,24 @@ class Wallet {
2524
throw e;
2625
}
2726
}
27+
}
28+
29+
async createParentWallet(keycloakId, wallet, about) {
30+
await this.checkWalletName(wallet);
31+
32+
const newWallet = await this._walletRepository.create({
33+
name: wallet,
34+
about,
35+
keycloak_account_id: keycloakId,
36+
});
37+
38+
return newWallet;
39+
}
40+
41+
async createWallet(loggedInWalletId, wallet, about) {
42+
// check name
43+
await this.checkWalletName(wallet);
2844

29-
// TO DO: check if wallet is expected format type?
3045
// TO DO: Need to check account permissions -> manage accounts
3146

3247
// need to create a wallet object

server/models/Wallet.spec.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('Wallet Model', () => {
3131
sinon.restore();
3232
});
3333

34-
describe('createWallet function', async () => {
34+
describe('createWallet function', () => {
3535
const walletId = uuid();
3636
const wallet = 'wallet';
3737

@@ -94,6 +94,67 @@ describe('Wallet Model', () => {
9494
});
9595
});
9696

97+
describe('createParentWallet function', () => {
98+
const wallet = 'wallet';
99+
100+
it('should error out, wallet already exists', async () => {
101+
walletRepositoryStub.getByName.resolves();
102+
let error;
103+
104+
try {
105+
await walletModel.createParentWallet(uuid(), wallet);
106+
} catch (e) {
107+
error = e;
108+
}
109+
110+
expect(error.code).eql(409);
111+
expect(error.message).eql(`The wallet "wallet" already exists`);
112+
expect(walletRepositoryStub.getByName).calledOnceWithExactly('wallet');
113+
expect(trustRepositoryStub.create).not.called;
114+
expect(walletRepositoryStub.create).not.called;
115+
});
116+
117+
it('should error out, some other error', async () => {
118+
walletRepositoryStub.getByName.rejects('internal error');
119+
let error;
120+
121+
try {
122+
await walletModel.createParentWallet(uuid(), wallet);
123+
} catch (e) {
124+
error = e;
125+
}
126+
127+
expect(error.toString()).eql(`internal error`);
128+
expect(walletRepositoryStub.getByName).calledOnceWithExactly('wallet');
129+
expect(trustRepositoryStub.create).not.called;
130+
expect(walletRepositoryStub.create).not.called;
131+
});
132+
133+
it('should create default wallet', async () => {
134+
walletRepositoryStub.getByName.rejects(new HttpError(404));
135+
const newWalletId = uuid();
136+
const keycloakId = uuid();
137+
const about = 'default wallet about';
138+
139+
walletRepositoryStub.create.resolves({ id: newWalletId });
140+
141+
const result = await walletModel.createParentWallet(
142+
keycloakId,
143+
wallet,
144+
about,
145+
);
146+
147+
expect(result).eql({ id: newWalletId });
148+
expect(trustRepositoryStub.create).not.called;
149+
expect(walletRepositoryStub.getByName).calledOnceWithExactly('wallet');
150+
expect(walletRepositoryStub.create).calledOnceWithExactly({
151+
name: wallet,
152+
about,
153+
keycloak_account_id: keycloakId,
154+
});
155+
});
156+
});
157+
97158
it('getById function', async () => {
98159
const walletId = uuid();
99160
walletRepositoryStub.getById.resolves({ id: walletId, wallet: 'wallet' });

server/repositories/WalletRepository.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ class WalletRepository extends BaseRepository {
6262
.table(this._tableName)
6363
.where('keycloak_account_id', keycloakAccountId)
6464
.first();
65-
if (!object) {
66-
throw new HttpError(404, `User is not associated with any wallet`);
67-
}
65+
6866
return object;
6967
}
7068

server/services/S3Service.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const upload = async (file, key, mimetype) => {
1414
const command = new PutObjectCommand(params);
1515
await s3.send(command);
1616

17-
return `https://${bucket}.s3-${region}.amazonaws.com/${key}`;
17+
return `https://${bucket}.s3.${region}.amazonaws.com/${encodeURIComponent(key)}`;
1818
};
1919

2020
module.exports = {

0 commit comments

Comments
 (0)