You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Server: Added API end points to manage users
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							| @@ -277,7 +277,6 @@ export interface User extends WithDates, WithUuid { | ||||
| 	is_admin?: number; | ||||
| 	max_item_size?: number; | ||||
| 	can_share?: number; | ||||
| 	max_share_recipients?: number; | ||||
| } | ||||
|  | ||||
| export interface Session extends WithDates, WithUuid { | ||||
| @@ -382,7 +381,6 @@ export const databaseSchema: DatabaseTables = { | ||||
| 		created_time: { type: 'string' }, | ||||
| 		max_item_size: { type: 'number' }, | ||||
| 		can_share: { type: 'number' }, | ||||
| 		max_share_recipients: { type: 'number' }, | ||||
| 	}, | ||||
| 	sessions: { | ||||
| 		id: { type: 'string' }, | ||||
|   | ||||
							
								
								
									
										80
									
								
								packages/server/src/routes/api/users.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								packages/server/src/routes/api/users.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| import { User } from '../../db'; | ||||
| import { deleteApi, getApi, patchApi, postApi } from '../../utils/testing/apiUtils'; | ||||
| import { beforeAllDb, afterAllTests, beforeEachDb, createUserAndSession, models } from '../../utils/testing/testUtils'; | ||||
|  | ||||
| describe('api_users', function() { | ||||
|  | ||||
| 	beforeAll(async () => { | ||||
| 		await beforeAllDb('api_users'); | ||||
| 	}); | ||||
|  | ||||
| 	afterAll(async () => { | ||||
| 		await afterAllTests(); | ||||
| 	}); | ||||
|  | ||||
| 	beforeEach(async () => { | ||||
| 		await beforeEachDb(); | ||||
| 	}); | ||||
|  | ||||
| 	test('should create a user', async function() { | ||||
| 		const { session: adminSession } = await createUserAndSession(1, true); | ||||
|  | ||||
| 		const userToSave: User = { | ||||
| 			full_name: 'Toto', | ||||
| 			email: 'toto@example.com', | ||||
| 			max_item_size: 1000, | ||||
| 			can_share: 0, | ||||
| 		}; | ||||
|  | ||||
| 		await postApi(adminSession.id, 'users', userToSave); | ||||
|  | ||||
| 		const savedUser = await models().user().loadByEmail('toto@example.com'); | ||||
| 		expect(savedUser.full_name).toBe('Toto'); | ||||
| 		expect(savedUser.email).toBe('toto@example.com'); | ||||
| 		expect(savedUser.can_share).toBe(0); | ||||
| 		expect(savedUser.max_item_size).toBe(1000); | ||||
| 	}); | ||||
|  | ||||
| 	test('should patch a user', async function() { | ||||
| 		const { session: adminSession } = await createUserAndSession(1, true); | ||||
| 		const { user } = await createUserAndSession(2); | ||||
|  | ||||
| 		await patchApi(adminSession.id, `users/${user.id}`, { | ||||
| 			max_item_size: 1000, | ||||
| 		}); | ||||
|  | ||||
| 		const savedUser = await models().user().load(user.id); | ||||
| 		expect(savedUser.max_item_size).toBe(1000); | ||||
| 	}); | ||||
|  | ||||
| 	test('should get a user', async function() { | ||||
| 		const { session: adminSession } = await createUserAndSession(1, true); | ||||
| 		const { user } = await createUserAndSession(2); | ||||
|  | ||||
| 		const fetchedUser: User = await getApi(adminSession.id, `users/${user.id}`); | ||||
|  | ||||
| 		expect(fetchedUser.id).toBe(user.id); | ||||
| 		expect(fetchedUser.email).toBe(user.email); | ||||
| 	}); | ||||
|  | ||||
| 	test('should delete a user', async function() { | ||||
| 		const { session: adminSession } = await createUserAndSession(1, true); | ||||
| 		const { user } = await createUserAndSession(2); | ||||
|  | ||||
| 		await deleteApi(adminSession.id, `users/${user.id}`); | ||||
|  | ||||
| 		const loadedUser = await models().user().load(user.id); | ||||
| 		expect(loadedUser).toBeFalsy(); | ||||
| 	}); | ||||
|  | ||||
| 	test('should list users', async function() { | ||||
| 		const { session: adminSession } = await createUserAndSession(1, true); | ||||
| 		await createUserAndSession(2); | ||||
| 		await createUserAndSession(3); | ||||
|  | ||||
| 		const results: any = await getApi(adminSession.id, 'users'); | ||||
| 		console.info(results); | ||||
| 		expect(results.items.length).toBe(3); | ||||
| 	}); | ||||
|  | ||||
| }); | ||||
							
								
								
									
										61
									
								
								packages/server/src/routes/api/users.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								packages/server/src/routes/api/users.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| import { User } from '../../db'; | ||||
| import { bodyFields } from '../../utils/requestUtils'; | ||||
| import { SubPath } from '../../utils/routeUtils'; | ||||
| import Router from '../../utils/Router'; | ||||
| import { AppContext } from '../../utils/types'; | ||||
| import { ErrorNotFound } from '../../utils/errors'; | ||||
| import { AclAction } from '../../models/BaseModel'; | ||||
| import uuidgen from '../../utils/uuidgen'; | ||||
|  | ||||
| const router = new Router(); | ||||
|  | ||||
| async function fetchUser(path: SubPath, ctx: AppContext): Promise<User> { | ||||
| 	const user = await ctx.models.user().load(path.id); | ||||
| 	if (!user) throw new ErrorNotFound(`No user with ID ${path.id}`); | ||||
| 	return user; | ||||
| } | ||||
|  | ||||
| async function postedUserFromContext(ctx: AppContext): Promise<User> { | ||||
| 	return ctx.models.user().fromApiInput(await bodyFields<any>(ctx.req)); | ||||
| } | ||||
|  | ||||
| router.get('api/users/:id', async (path: SubPath, ctx: AppContext) => { | ||||
| 	const user = await fetchUser(path, ctx); | ||||
| 	await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Read, user); | ||||
| 	return user; | ||||
| }); | ||||
|  | ||||
| router.post('api/users', async (_path: SubPath, ctx: AppContext) => { | ||||
| 	await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Create); | ||||
| 	const user = await postedUserFromContext(ctx); | ||||
|  | ||||
| 	// We set a random password because it's required, but user will have to | ||||
| 	// set it by clicking on the confirmation link. | ||||
| 	user.password = uuidgen(); | ||||
| 	const output = await ctx.models.user().save(user); | ||||
| 	return ctx.models.user().toApiOutput(output); | ||||
| }); | ||||
|  | ||||
| router.get('api/users', async (_path: SubPath, ctx: AppContext) => { | ||||
| 	await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.List); | ||||
|  | ||||
| 	return { | ||||
| 		items: await ctx.models.user().all(), | ||||
| 		has_more: false, | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
| router.del('api/users/:id', async (path: SubPath, ctx: AppContext) => { | ||||
| 	const user = await fetchUser(path, ctx); | ||||
| 	await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Delete, user); | ||||
| 	await ctx.models.user().delete(user.id); | ||||
| }); | ||||
|  | ||||
| router.patch('api/users/:id', async (path: SubPath, ctx: AppContext) => { | ||||
| 	const user = await fetchUser(path, ctx); | ||||
| 	await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Update, user); | ||||
| 	const postedUser = await postedUserFromContext(ctx); | ||||
| 	await ctx.models.user().save({ id: user.id, ...postedUser }); | ||||
| }); | ||||
|  | ||||
| export default router; | ||||
| @@ -5,6 +5,7 @@ import apiEvents from './api/events'; | ||||
| import apiItems from './api/items'; | ||||
| import apiPing from './api/ping'; | ||||
| import apiSessions from './api/sessions'; | ||||
| import apiUsers from './api/users'; | ||||
| import apiShares from './api/shares'; | ||||
| import apiShareUsers from './api/share_users'; | ||||
|  | ||||
| @@ -27,6 +28,7 @@ const routes: Routers = { | ||||
| 	'api/sessions': apiSessions, | ||||
| 	'api/share_users': apiShareUsers, | ||||
| 	'api/shares': apiShares, | ||||
| 	'api/users': apiUsers, | ||||
|  | ||||
| 	'changes': indexChanges, | ||||
| 	'home': indexHome, | ||||
|   | ||||
| @@ -242,7 +242,7 @@ export const createUserAndSession = async function(index: number = 1, isAdmin: b | ||||
| 	const session = await models().session().authenticate(options.email, options.password); | ||||
|  | ||||
| 	return { | ||||
| 		user: user, | ||||
| 		user: await models().user().load(user.id), | ||||
| 		session: session, | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user