Skip to content

Commit 0144890

Browse files
authored
[TOOLS-4858] Database user CRUD API (#37)
* feature : getUserInfo, createUser, updateUser(modified), deleteUser, userVerify API implemented * fix : mismatch between entry point and validation route fixed
1 parent ac8c89b commit 0144890

26 files changed

+990
-16
lines changed

WebCA Server API.postman_collection.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,56 @@
11411141
{
11421142
"name": "Database Users",
11431143
"item": [
1144+
{
1145+
"name": "Get User Info (userinfo)",
1146+
"request": {
1147+
"method": "GET",
1148+
"header": [],
1149+
"url": {
1150+
"raw": "{{base_url}}/{{hostUid}}/database/users/info/{{dbname}}",
1151+
"host": ["{{base_url}}"],
1152+
"path": ["{{hostUid}}", "database", "users", "info", "{{dbname}}"]
1153+
},
1154+
"description": "Get user info (list of users) for a database. CMS task: userinfo. GET /:hostUid/database/users/info/:dbname. Auth: Bearer JWT."
1155+
},
1156+
"response": []
1157+
},
1158+
{
1159+
"name": "User Verify (userverify)",
1160+
"request": {
1161+
"method": "POST",
1162+
"header": [{"key": "Content-Type", "value": "application/json"}],
1163+
"body": {
1164+
"mode": "raw",
1165+
"raw": "{\n \"dbname\": \"demodb\",\n \"dbuser\": \"dba\",\n \"dbpasswd\": \"\"\n}"
1166+
},
1167+
"url": {
1168+
"raw": "{{base_url}}/{{hostUid}}/database/users/verify",
1169+
"host": ["{{base_url}}"],
1170+
"path": ["{{hostUid}}", "database", "users", "verify"]
1171+
},
1172+
"description": "Verify database user credentials. CMS task: userverify. POST /:hostUid/database/users/verify. Body: dbname, dbuser, dbpasswd. Auth: Bearer JWT."
1173+
},
1174+
"response": []
1175+
},
1176+
{
1177+
"name": "Create Database User (createuser)",
1178+
"request": {
1179+
"method": "POST",
1180+
"header": [{"key": "Content-Type", "value": "application/json"}],
1181+
"body": {
1182+
"mode": "raw",
1183+
"raw": "{\n \"dbname\": \"demodb\",\n \"username\": \"yifan\",\n \"userpass\": \"1111\",\n \"groups\": { \"group\": [\"public\"] },\n \"authorization\": []\n}"
1184+
},
1185+
"url": {
1186+
"raw": "{{base_url}}/{{hostUid}}/database/users",
1187+
"host": ["{{base_url}}"],
1188+
"path": ["{{hostUid}}", "database", "users"]
1189+
},
1190+
"description": "Create a database user. CMS task: createuser. POST /:hostUid/database/users. Body: dbname, username, userpass, groups, authorization. Auth: Bearer JWT."
1191+
},
1192+
"response": []
1193+
},
11441194
{
11451195
"name": "Get Database Users",
11461196
"request": {
@@ -1232,6 +1282,20 @@
12321282
"description": "Update a database user. Returns empty object on success.\n\n**URI:**\n```\nPUT /:hostUid/database/users/:dbname/:username\n```\n**Example:**\n```\nPUT /host-uid-1/database/users/demodb/yifan\n```\n\n**Authentication:** Required\n**Headers:**\n```\nAuthorization: Bearer <JWT_TOKEN>\nContent-Type: application/json\n```\n\n**Path Parameters:**\n- `hostUid` - Host unique identifier\n- `dbname` - Database name\n- `username` - Username to update\n\n**Request Body:**\n```json\n{\n \"userpass\": \"1111\",\n \"groups\": {\n \"group\": [\"public\"]\n },\n \"authorization\": []\n}\n```\n\n**Success Response (200 OK):**\n```json\n{}\n```\n\n**Error Response (400 Bad Request):**\n```json\n{\n \"statusCode\": 400,\n \"message\": \"Missing required fields: userpass, groups, authorization\",\n \"error\": \"Bad Request\"\n}\n```\n\n**Error Response (404 Not Found):**\n```json\n{\n \"statusCode\": 404,\n \"message\": \"Database or user not found\",\n \"error\": \"Not Found\"\n}\n```"
12331283
},
12341284
"response": []
1285+
},
1286+
{
1287+
"name": "Delete Database User (deleteuser)",
1288+
"request": {
1289+
"method": "DELETE",
1290+
"header": [],
1291+
"url": {
1292+
"raw": "{{base_url}}/{{hostUid}}/database/users/{{dbname}}/{{username}}",
1293+
"host": ["{{base_url}}"],
1294+
"path": ["{{hostUid}}", "database", "users", "{{dbname}}", "{{username}}"]
1295+
},
1296+
"description": "Delete a database user. CMS task: deleteuser. DELETE /:hostUid/database/users/:dbname/:username. Auth: Bearer JWT."
1297+
},
1298+
"response": []
12351299
}
12361300
],
12371301
"description": "Database user management endpoints. All endpoints require JWT authentication."

apps/api-server/src/database/user/database-user.controller.spec.ts

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,188 @@ import { DatabaseUserService } from './database-user.service';
44

55
describe('DatabaseUserController', () => {
66
let controller: DatabaseUserController;
7-
let service: DatabaseUserService;
7+
let service: jest.Mocked<DatabaseUserService>;
8+
9+
const mockReq = { user: { sub: 'user-123' } };
810

911
beforeEach(async () => {
12+
const mockDatabaseUserService = {
13+
getDatabaseUsers: jest.fn(),
14+
getUserInfo: jest.fn(),
15+
userVerify: jest.fn(),
16+
createUser: jest.fn(),
17+
deleteUser: jest.fn(),
18+
loginDatabase: jest.fn(),
19+
updateUser: jest.fn(),
20+
};
21+
1022
const module: TestingModule = await Test.createTestingModule({
1123
controllers: [DatabaseUserController],
1224
providers: [
1325
{
1426
provide: DatabaseUserService,
15-
useValue: {
16-
getDatabaseUsers: jest.fn(),
17-
},
27+
useValue: mockDatabaseUserService,
1828
},
1929
],
2030
}).compile();
2131

22-
controller = module.get<DatabaseUserController>(DatabaseUserController);
23-
service = module.get<DatabaseUserService>(DatabaseUserService);
32+
controller = module.get(DatabaseUserController);
33+
service = module.get(DatabaseUserService);
2434
});
2535

2636
it('should be defined', () => {
2737
expect(controller).toBeDefined();
2838
});
39+
40+
describe('getDatabaseUsers', () => {
41+
it('should call service.getDatabaseUsers and return result', async () => {
42+
service.getDatabaseUsers.mockResolvedValue([]);
43+
44+
const result = await controller.getDatabaseUsers(mockReq, 'host-uid-1');
45+
46+
expect(service.getDatabaseUsers).toHaveBeenCalledWith('user-123');
47+
expect(result).toEqual([]);
48+
});
49+
});
50+
51+
describe('getUserInfo', () => {
52+
it('should call service.getUserInfo and return dbname and user list', async () => {
53+
const mockResponse = {
54+
dbname: 'demodb',
55+
user: [{ '@id': '163810704', '@name': 'PUBLIC' }],
56+
};
57+
service.getUserInfo.mockResolvedValue(mockResponse);
58+
59+
const result = await controller.getUserInfo(
60+
mockReq,
61+
'host-uid-1',
62+
'demodb'
63+
);
64+
65+
expect(service.getUserInfo).toHaveBeenCalledWith(
66+
'user-123',
67+
'host-uid-1',
68+
'demodb'
69+
);
70+
expect(result).toEqual(mockResponse);
71+
});
72+
});
73+
74+
describe('userVerify', () => {
75+
it('should call service.userVerify and return { verified: true }', async () => {
76+
const body = { dbname: 'demodb', dbuser: 'dba', dbpasswd: '' };
77+
service.userVerify.mockResolvedValue({ verified: true });
78+
79+
const result = await controller.userVerify(mockReq, 'host-uid-1', body);
80+
81+
expect(service.userVerify).toHaveBeenCalledWith(
82+
'user-123',
83+
'host-uid-1',
84+
'demodb',
85+
'dba',
86+
''
87+
);
88+
expect(result).toEqual({ verified: true });
89+
});
90+
});
91+
92+
describe('createUser', () => {
93+
it('should call service.createUser and return empty object', async () => {
94+
const body = {
95+
dbname: 'demodb',
96+
username: 'yifan',
97+
userpass: '1111',
98+
groups: { group: ['public'] },
99+
authorization: [],
100+
};
101+
service.createUser.mockResolvedValue({});
102+
103+
const result = await controller.createUser(mockReq, 'host-uid-1', body);
104+
105+
expect(service.createUser).toHaveBeenCalledWith(
106+
'user-123',
107+
'host-uid-1',
108+
body.dbname,
109+
body.username,
110+
body.userpass,
111+
body.groups,
112+
body.authorization
113+
);
114+
expect(result).toEqual({});
115+
});
116+
});
117+
118+
describe('deleteUser', () => {
119+
it('should call service.deleteUser and return empty object', async () => {
120+
service.deleteUser.mockResolvedValue({});
121+
122+
const result = await controller.deleteUser(
123+
mockReq,
124+
'host-uid-1',
125+
'demodb',
126+
'yifan'
127+
);
128+
129+
expect(service.deleteUser).toHaveBeenCalledWith(
130+
'user-123',
131+
'host-uid-1',
132+
'demodb',
133+
'yifan'
134+
);
135+
expect(result).toEqual({});
136+
});
137+
});
138+
139+
describe('updateUser', () => {
140+
it('should call service.updateUser and return empty object', async () => {
141+
const body = {
142+
userpass: '1111',
143+
groups: { group: ['public'] },
144+
authorization: [],
145+
};
146+
service.updateUser.mockResolvedValue({});
147+
148+
const result = await controller.updateUser(
149+
mockReq,
150+
'host-uid-1',
151+
'demodb',
152+
'yifan',
153+
body
154+
);
155+
156+
expect(service.updateUser).toHaveBeenCalledWith(
157+
'user-123',
158+
'host-uid-1',
159+
'demodb',
160+
'yifan',
161+
body.userpass,
162+
body.groups,
163+
body.authorization
164+
);
165+
expect(result).toEqual({});
166+
});
167+
});
168+
169+
describe('loginDatabase', () => {
170+
it('should call service.loginDatabase with id and password when body has them', async () => {
171+
const body = { id: 'dba', password: 'pass' };
172+
service.loginDatabase.mockResolvedValue(true);
173+
174+
const result = await controller.loginDatabase(
175+
mockReq,
176+
'host-uid-1',
177+
'demodb',
178+
body
179+
);
180+
181+
expect(service.loginDatabase).toHaveBeenCalledWith(
182+
'user-123',
183+
'host-uid-1',
184+
'demodb',
185+
'dba',
186+
'pass'
187+
);
188+
expect(result).toBe(true);
189+
});
190+
});
29191
});

apps/api-server/src/database/user/database-user.controller.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1-
import { Body, Controller, Get, Logger, Param, Post, Put, Request } from '@nestjs/common';
1+
import {
2+
Body,
3+
Controller,
4+
Delete,
5+
Get,
6+
Logger,
7+
Param,
8+
Post,
9+
Put,
10+
Request,
11+
} from '@nestjs/common';
212
import { DatabaseUserService } from './database-user.service';
313
import {
14+
CreateDbUserRequest,
415
DatabaseLoginClientRequest,
516
UpdateDbUserRequest,
617
UpdateDbUserResponse,
18+
UserInfoClientResponse,
19+
CreateDbUserResponse,
20+
DeleteDbUserResponse,
21+
UserVerifyRequest,
22+
UserVerifyResponse,
723
} from '@api-interfaces';
824
import { ValidationError } from '@error/validation/validation-error';
925
import { validateRequiredFields } from '@util';
@@ -23,6 +39,92 @@ export class DatabaseUserController {
2339

2440
constructor(private readonly databaseUserService: DatabaseUserService) {}
2541

42+
/**
43+
* Get user info (list of users) for a database. CMS task: userinfo.
44+
*
45+
* @route GET /:hostUid/database/users/info/:dbname
46+
*/
47+
@Get('info/:dbname')
48+
async getUserInfo(
49+
@Request() req,
50+
@Param('hostUid') hostUid: string,
51+
@Param('dbname') dbname: string
52+
): Promise<UserInfoClientResponse> {
53+
const userId = req.user.sub;
54+
this.logger.log(`Getting user info for database: ${dbname} on host: ${hostUid}`);
55+
return await this.databaseUserService.getUserInfo(userId, hostUid, dbname);
56+
}
57+
58+
/**
59+
* Verify database user credentials. CMS task: userverify.
60+
*
61+
* @route POST /:hostUid/database/users/verify
62+
*/
63+
@Post('verify')
64+
async userVerify(
65+
@Request() req,
66+
@Param('hostUid') hostUid: string,
67+
@Body() body: UserVerifyRequest
68+
): Promise<UserVerifyResponse> {
69+
const userId = req.user.sub;
70+
validateRequiredFields(body, ['dbname', 'dbuser', 'dbpasswd'], 'database/users/verify', this.logger);
71+
this.logger.log(`Verifying user ${body.dbuser} for database: ${body.dbname} on host: ${hostUid}`);
72+
return await this.databaseUserService.userVerify(
73+
userId,
74+
hostUid,
75+
body.dbname,
76+
body.dbuser,
77+
body.dbpasswd
78+
);
79+
}
80+
81+
/**
82+
* Create a database user. CMS task: createuser.
83+
*
84+
* @route POST /:hostUid/database/users
85+
*/
86+
@Post()
87+
async createUser(
88+
@Request() req,
89+
@Param('hostUid') hostUid: string,
90+
@Body() body: CreateDbUserRequest
91+
): Promise<CreateDbUserResponse> {
92+
const userId = req.user.sub;
93+
validateRequiredFields(
94+
body,
95+
['dbname', 'username', 'userpass', 'groups', 'authorization'],
96+
'database/users',
97+
this.logger
98+
);
99+
this.logger.log(`Creating user: ${body.username} in database: ${body.dbname} on host: ${hostUid}`);
100+
return await this.databaseUserService.createUser(
101+
userId,
102+
hostUid,
103+
body.dbname,
104+
body.username,
105+
body.userpass,
106+
body.groups,
107+
body.authorization
108+
);
109+
}
110+
111+
/**
112+
* Delete a database user. CMS task: deleteuser.
113+
*
114+
* @route DELETE /:hostUid/database/users/:dbname/:username
115+
*/
116+
@Delete(':dbname/:username')
117+
async deleteUser(
118+
@Request() req,
119+
@Param('hostUid') hostUid: string,
120+
@Param('dbname') dbname: string,
121+
@Param('username') username: string
122+
): Promise<DeleteDbUserResponse> {
123+
const userId = req.user.sub;
124+
this.logger.log(`Deleting user: ${username} from database: ${dbname} on host: ${hostUid}`);
125+
return await this.databaseUserService.deleteUser(userId, hostUid, dbname, username);
126+
}
127+
26128
/**
27129
* Get list of database users for a specific host.
28130
*

0 commit comments

Comments
 (0)