diff --git a/lib/core/users/usecase.ts b/lib/core/users/usecase.ts index 22747574..e05ca15e 100644 --- a/lib/core/users/usecase.ts +++ b/lib/core/users/usecase.ts @@ -77,6 +77,19 @@ export class UsersUsecase { private eventBus: EventBus ) {} + async getUserUsage(uuid: User['uuid']): Promise { + const user = await this.usersRepository.findByUuid(uuid); + + if (!user) { + throw new UserNotFoundError(uuid); + } + + return { + maxSpaceBytes: user.maxSpaceBytes, + totalUsedSpaceBytes: user.totalUsedSpaceBytes, + }; + } + async updateEmail(uuid: User['uuid'], newEmail: User['email']): Promise { const [maybeAlreadyExistentUser, userToUpdate] = await Promise.all([ this.usersRepository.findByEmail(newEmail), diff --git a/lib/server/http/gateway/controller.ts b/lib/server/http/gateway/controller.ts index 4d7a83c2..5c1a180d 100644 --- a/lib/server/http/gateway/controller.ts +++ b/lib/server/http/gateway/controller.ts @@ -1,4 +1,5 @@ import { Request, Response } from 'express'; +import { validate as uuidValidate } from 'uuid'; import { Logger } from 'winston'; import { EmailIsAlreadyInUseError, InvalidDataFormatError, UserAlreadyExistsError, UserNotFoundError, UserSpaceSnapshot, UsersUsecase } from '../../../core'; import { BucketEntriesUsecase } from '../../../core/bucketEntries/usecase'; @@ -34,6 +35,36 @@ export class HTTPGatewayController { private eventBus: EventBus ) {} + async getUserUsage( + req: Request<{ uuid: string }, {}, {}, {}>, + res: Response + ) { + const { uuid } = req.params; + + if (!uuid || !uuidValidate(uuid)) { + return res.status(400).send({ message: 'Missing or invalid uuid' }); + } + + try { + const usage = await this.usersUsecase.getUserUsage(uuid); + + return res.status(200).send(usage); + } catch (err) { + if (err instanceof UserNotFoundError) { + return res.status(404).send({ message: err.message }); + } + + this.logger.error( + '[GATEWAY/GET_USAGE] Error getting usage of user %s: %s. %s', + uuid, + (err as Error).message, + (err as Error).stack || 'NO STACK' + ); + + return res.status(500).send({ message: 'Internal server error' }); + } + } + async updateUserEmail(req: Request<{ uuid: string }, {}, { email?: string }, {}>, res: Response) { if (!(req.body && req.body.email && req.params.uuid)) { return res.status(400).send({ error: "Missing params" }); diff --git a/lib/server/http/gateway/index.ts b/lib/server/http/gateway/index.ts index cf851cb6..52acc390 100644 --- a/lib/server/http/gateway/index.ts +++ b/lib/server/http/gateway/index.ts @@ -8,6 +8,7 @@ export const createGatewayHTTPRouter = ( const router = Router(); router.post('/users', jwtMiddleware, controller.findOrCreateUser.bind(controller)); + router.get('/users/:uuid/usage', jwtMiddleware, controller.getUserUsage.bind(controller)); router.patch('/users/:uuid', jwtMiddleware, controller.updateUserEmail.bind(controller)); router.put('/storage/users/:uuid', jwtMiddleware, controller.changeStorage.bind(controller)); router.post('/users/:uuid/buckets', jwtMiddleware, controller.createUserBucket.bind(controller)); diff --git a/tests/lib/core/users/usecase.test.ts b/tests/lib/core/users/usecase.test.ts index e49d28fb..b0b3c0ad 100644 --- a/tests/lib/core/users/usecase.test.ts +++ b/tests/lib/core/users/usecase.test.ts @@ -68,6 +68,31 @@ const fakeUser: User = { } describe('Users usecases', () => { + describe('getUserUsage()', () => { + it('Should return the user space snapshot if the user exists', async () => { + const user = { ...fakeUser, maxSpaceBytes: 5000, totalUsedSpaceBytes: 1200 }; + stub(usersRepository, 'findByUuid').resolves(user); + + const usage = await usecase.getUserUsage(user.uuid); + + expect(usage).toStrictEqual({ + maxSpaceBytes: 5000, + totalUsedSpaceBytes: 1200 + }); + }); + + it('Should reject if the user does not exist', async () => { + stub(usersRepository, 'findByUuid').resolves(null); + + try { + await usecase.getUserUsage('unknown-uuid'); + expect(true).toBeFalsy(); + } catch (err) { + expect(err).toBeInstanceOf(UserNotFoundError); + } + }); + }); + describe('createUser()', () => { it(`Should work if input data is valid`, async () => { stub(usersRepository, 'findByEmail').resolves(null); diff --git a/tests/lib/e2e/gateway/gateway-v2.e2e-spec.ts b/tests/lib/e2e/gateway/gateway-v2.e2e-spec.ts index c53858af..171188db 100644 --- a/tests/lib/e2e/gateway/gateway-v2.e2e-spec.ts +++ b/tests/lib/e2e/gateway/gateway-v2.e2e-spec.ts @@ -44,6 +44,60 @@ describe('Gateway V2 e2e tests', () => { }) }) + describe('Getting user usage', () => { + it('When getting the usage of an existing user, then it should return the space snapshot', async () => { + const testUser = await createTestUser() + + const jwt = signRS256JWT( + '5m', + engine._config.gateway.SIGN_JWT_SECRET, + ); + + const response = await testServer + .get(`/v2/gateway/users/${testUser.uuid}/usage`) + .set('Authorization', `Bearer ${jwt}`) + + expect(response.status).toBe(200) + expect(response.body).toStrictEqual({ + maxSpaceBytes: testUser.maxSpaceBytes, + totalUsedSpaceBytes: testUser.totalUsedSpaceBytes, + }) + }) + + it('When getting the usage of a user that does not exist, then it should return 404', async () => { + const jwt = signRS256JWT( + '5m', + engine._config.gateway.SIGN_JWT_SECRET, + ); + + const response = await testServer + .get(`/v2/gateway/users/${crypto.randomUUID()}/usage`) + .set('Authorization', `Bearer ${jwt}`) + + expect(response.status).toBe(404) + }) + + it('When the uuid has an invalid format, then it should return 400', async () => { + const jwt = signRS256JWT( + '5m', + engine._config.gateway.SIGN_JWT_SECRET, + ); + + const response = await testServer + .get('/v2/gateway/users/not-a-valid-uuid/usage') + .set('Authorization', `Bearer ${jwt}`) + + expect(response.status).toBe(400) + }) + + it('When the request is not authenticated, then it should return 401', async () => { + const response = await testServer + .get(`/v2/gateway/users/${crypto.randomUUID()}/usage`) + + expect(response.status).toBe(401) + }) + }) + describe('Creating a user bucket', () => { it('When creating a bucket for a user, then it is persisted with the user uuid and given name', async () => { const testUser = await createTestUser()