mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	feat(writeapi): user settings API
This commit is contained in:
		
							
								
								
									
										143
									
								
								public/openapi/components/schemas/SettingsObj.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								public/openapi/components/schemas/SettingsObj.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| Settings: | ||||
|   type: object | ||||
|   properties: | ||||
|     showemail: | ||||
|       type: boolean | ||||
|       description: Show user email in profile page | ||||
|     usePagination: | ||||
|       type: boolean | ||||
|       description: Toggles between pagination (when enabled), or infinite scrolling (when disabled) | ||||
|     topicsPerPage: | ||||
|       type: number | ||||
|       description: Number of topics displayed on a category page | ||||
|     postsPerPage: | ||||
|       type: number | ||||
|       description: Number of posts displayed on a topic page | ||||
|     topicPostSort: | ||||
|       type: string | ||||
|       description: Default sorting strategy of the posts in of a topic | ||||
|     openOutgoingLinksInNewTab: | ||||
|       type: boolean | ||||
|       description: Whether to automatically open all external links in a new tab | ||||
|     dailyDigestFreq: | ||||
|       type: string | ||||
|       description: How often to receive the scheduled digest from this forum | ||||
|     showfullname: | ||||
|       type: boolean | ||||
|       description: Show user full name in profile page | ||||
|     followTopicsOnCreate: | ||||
|       type: boolean | ||||
|       description: Automatically be notified of new posts in a topic, when you create a topic | ||||
|     followTopicsOnReply: | ||||
|       type: boolean | ||||
|       description: | ||||
|     restrictChat: | ||||
|       type: boolean | ||||
|       description: | ||||
|     topicSearchEnabled: | ||||
|       type: boolean | ||||
|       description: | ||||
|     categoryTopicSort: | ||||
|       type: string | ||||
|       description: | ||||
|     userLang: | ||||
|       type: string | ||||
|       description: | ||||
|     bootswatchSkin: | ||||
|       type: string | ||||
|       description: | ||||
|     homePageRoute: | ||||
|       type: string | ||||
|       description: | ||||
|     scrollToMyPost: | ||||
|       type: boolean | ||||
|       description: | ||||
|     notificationType_new-chat: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_new-reply: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_post-edit: | ||||
|       type: string | ||||
|       description: | ||||
|     sendChatNotifications: | ||||
|       nullable: true | ||||
|     sendPostNotifications: | ||||
|       nullable: true | ||||
|     notificationType_upvote: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_new-topic: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_follow: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_group-invite: | ||||
|       type: string | ||||
|       description: | ||||
|     upvoteNotifFreq: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_mention: | ||||
|       type: string | ||||
|       description: | ||||
|     acpLang: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_new-register: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_post-queue: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_new-post-flag: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_new-user-flag: | ||||
|       type: string | ||||
|       description: | ||||
|     categoryWatchState: | ||||
|       type: string | ||||
|       description: | ||||
|     notificationType_group-request-membership: | ||||
|       type: string | ||||
|       description: | ||||
|     uid: | ||||
|       type: number | ||||
|       description: | ||||
|       description: A user identifier | ||||
|   required: | ||||
|     - showemail | ||||
|     - usePagination | ||||
|     - topicsPerPage | ||||
|     - postsPerPage | ||||
|     - topicPostSort | ||||
|     - openOutgoingLinksInNewTab | ||||
|     - dailyDigestFreq | ||||
|     - showfullname | ||||
|     - followTopicsOnCreate | ||||
|     - followTopicsOnReply | ||||
|     - restrictChat | ||||
|     - topicSearchEnabled | ||||
|     - categoryTopicSort | ||||
|     - userLang | ||||
|     - bootswatchSkin | ||||
|     - homePageRoute | ||||
|     - scrollToMyPost | ||||
|     - notificationType_new-chat | ||||
|     - notificationType_new-reply | ||||
|     - notificationType_upvote | ||||
|     - notificationType_new-topic | ||||
|     - notificationType_follow | ||||
|     - notificationType_group-invite | ||||
|     - upvoteNotifFreq | ||||
|     - acpLang | ||||
|     - notificationType_new-register | ||||
|     - notificationType_post-queue | ||||
|     - notificationType_new-post-flag | ||||
|     - notificationType_new-user-flag | ||||
|     - categoryWatchState | ||||
|     - notificationType_group-request-membership | ||||
|     - uid | ||||
| @@ -20,114 +20,7 @@ get: | ||||
|               - type: object | ||||
|                 properties: | ||||
|                   settings: | ||||
|                     type: object | ||||
|                     properties: | ||||
|                       showemail: | ||||
|                         type: boolean | ||||
|                       usePagination: | ||||
|                         type: boolean | ||||
|                       topicsPerPage: | ||||
|                         type: number | ||||
|                       postsPerPage: | ||||
|                         type: number | ||||
|                       topicPostSort: | ||||
|                         type: string | ||||
|                       openOutgoingLinksInNewTab: | ||||
|                         type: boolean | ||||
|                       dailyDigestFreq: | ||||
|                         type: string | ||||
|                       showfullname: | ||||
|                         type: boolean | ||||
|                       followTopicsOnCreate: | ||||
|                         type: boolean | ||||
|                       followTopicsOnReply: | ||||
|                         type: boolean | ||||
|                       restrictChat: | ||||
|                         type: boolean | ||||
|                       topicSearchEnabled: | ||||
|                         type: boolean | ||||
|                       categoryTopicSort: | ||||
|                         type: string | ||||
|                       userLang: | ||||
|                         type: string | ||||
|                       bootswatchSkin: | ||||
|                         type: string | ||||
|                       homePageRoute: | ||||
|                         type: string | ||||
|                       scrollToMyPost: | ||||
|                         type: boolean | ||||
|                       notificationType_new-chat: | ||||
|                         type: string | ||||
|                       notificationType_new-reply: | ||||
|                         type: string | ||||
|                       notificationType_post-edit: | ||||
|                         type: string | ||||
|                       sendChatNotifications: | ||||
|                         nullable: true | ||||
|                       sendPostNotifications: | ||||
|                         nullable: true | ||||
|                       notificationType_upvote: | ||||
|                         type: string | ||||
|                       notificationType_new-topic: | ||||
|                         type: string | ||||
|                       notificationType_follow: | ||||
|                         type: string | ||||
|                       notificationType_group-invite: | ||||
|                         type: string | ||||
|                       upvoteNotifFreq: | ||||
|                         type: string | ||||
|                       notificationType_mention: | ||||
|                         type: string | ||||
|                       acpLang: | ||||
|                         type: string | ||||
|                       notificationType_new-register: | ||||
|                         type: string | ||||
|                       notificationType_post-queue: | ||||
|                         type: string | ||||
|                       notificationType_new-post-flag: | ||||
|                         type: string | ||||
|                       notificationType_new-user-flag: | ||||
|                         type: string | ||||
|                       categoryWatchState: | ||||
|                         type: string | ||||
|                       notificationType_group-request-membership: | ||||
|                         type: string | ||||
|                       uid: | ||||
|                         type: number | ||||
|                         description: A user identifier | ||||
|                     required: | ||||
|                       - showemail | ||||
|                       - usePagination | ||||
|                       - topicsPerPage | ||||
|                       - postsPerPage | ||||
|                       - topicPostSort | ||||
|                       - openOutgoingLinksInNewTab | ||||
|                       - dailyDigestFreq | ||||
|                       - showfullname | ||||
|                       - followTopicsOnCreate | ||||
|                       - followTopicsOnReply | ||||
|                       - restrictChat | ||||
|                       - topicSearchEnabled | ||||
|                       - categoryTopicSort | ||||
|                       - userLang | ||||
|                       - bootswatchSkin | ||||
|                       - homePageRoute | ||||
|                       - scrollToMyPost | ||||
|                       - notificationType_new-chat | ||||
|                       - notificationType_new-reply | ||||
|                       - notificationType_upvote | ||||
|                       - notificationType_new-topic | ||||
|                       - notificationType_follow | ||||
|                       - notificationType_group-invite | ||||
|                       - upvoteNotifFreq | ||||
|                       - acpLang | ||||
|                       - notificationType_new-register | ||||
|                       - notificationType_post-queue | ||||
|                       - notificationType_new-post-flag | ||||
|                       - notificationType_new-user-flag | ||||
|                       - categoryWatchState | ||||
|                       - notificationType_group-request-membership | ||||
|                       - uid | ||||
|                     $ref: ../../../components/schemas/SettingsObj.yaml#/Settings | ||||
|                   languages: | ||||
|                     type: array | ||||
|                     items: | ||||
|   | ||||
| @@ -30,6 +30,10 @@ tags: | ||||
| paths: | ||||
|   /users/{uid}: | ||||
|     $ref: 'write/users/uid.yaml' | ||||
|   /users/{uid}/settings: | ||||
|     $ref: 'write/users/uid/settings.yaml' | ||||
|   /users/{uid}/settings/{setting}: | ||||
|     $ref: 'write/users/uid/settings/setting.yaml' | ||||
|   /users/{uid}/password: | ||||
|     $ref: 'write/users/uid/password.yaml' | ||||
|   /users/{uid}/follow: | ||||
|   | ||||
							
								
								
									
										35
									
								
								public/openapi/write/users/uid/settings.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								public/openapi/write/users/uid/settings.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| put: | ||||
|   tags: | ||||
|     - users | ||||
|   summary: update user settings | ||||
|   parameters: | ||||
|     - in: path | ||||
|       name: uid | ||||
|       schema: | ||||
|         type: integer | ||||
|       required: true | ||||
|       description: uid of the user | ||||
|   requestBody: | ||||
|     content: | ||||
|       application/json: | ||||
|         schema: | ||||
|           type: object | ||||
|           properties: | ||||
|             settings: | ||||
|               type: object | ||||
|               description: An object containing key-value pairs of user settings to update | ||||
|               example: | ||||
|                 showemail: '0' | ||||
|                 showfullname: '1' | ||||
|   responses: | ||||
|     '200': | ||||
|       description: successfully updated user settings | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             type: object | ||||
|             properties: | ||||
|               status: | ||||
|                 $ref: ../../../components/schemas/Status.yaml#/Status | ||||
|               response: | ||||
|                 $ref: ../../../components/schemas/SettingsObj.yaml#/Settings | ||||
							
								
								
									
										40
									
								
								public/openapi/write/users/uid/settings/setting.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								public/openapi/write/users/uid/settings/setting.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| put: | ||||
|   tags: | ||||
|     - users | ||||
|   summary: update one user setting | ||||
|   parameters: | ||||
|     - in: path | ||||
|       name: uid | ||||
|       schema: | ||||
|         type: integer | ||||
|       required: true | ||||
|       description: uid of the user | ||||
|       example: '1' | ||||
|     - in: path | ||||
|       name: setting | ||||
|       schema: | ||||
|         type: string | ||||
|       required: true | ||||
|       description: name of the setting you wish to update | ||||
|       example: 'showemail' | ||||
|   requestBody: | ||||
|     content: | ||||
|       application/json: | ||||
|         schema: | ||||
|           type: object | ||||
|           properties: | ||||
|             value: | ||||
|               type: string | ||||
|               example: '1' | ||||
|   responses: | ||||
|     '200': | ||||
|       description: successfully updated user settings | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             type: object | ||||
|             properties: | ||||
|               status: | ||||
|                 $ref: ../../../../components/schemas/Status.yaml#/Status | ||||
|               response: | ||||
|                 type: object | ||||
| @@ -1,7 +1,7 @@ | ||||
| 'use strict'; | ||||
|  | ||||
|  | ||||
| define('forum/account/settings', ['forum/account/header', 'components', 'translator'], function (header, components, translator) { | ||||
| define('forum/account/settings', ['forum/account/header', 'components', 'translator', 'api'], function (header, components, translator, api) { | ||||
| 	var	AccountSettings = {}; | ||||
|  | ||||
| 	// If page skin is changed but not saved, switch the skin back | ||||
| @@ -67,11 +67,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'transla | ||||
| 	} | ||||
|  | ||||
| 	function saveSettings(settings) { | ||||
| 		socket.emit('user.saveSettings', { uid: ajaxify.data.theirid, settings: settings }, function (err, newSettings) { | ||||
| 			if (err) { | ||||
| 				return app.alertError(err.message); | ||||
| 			} | ||||
|  | ||||
| 		api.put(`/users/${ajaxify.data.uid}/settings`, { settings }).then((newSettings) => { | ||||
| 			app.alertSuccess('[[success:settings-saved]]'); | ||||
| 			var languageChanged = false; | ||||
| 			for (var key in newSettings) { | ||||
|   | ||||
| @@ -24,7 +24,7 @@ define('forum/category', [ | ||||
|  | ||||
| 		topicList.init('category', loadTopicsAfter); | ||||
|  | ||||
| 		sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug); | ||||
| 		sort.handleSort('categoryTopicSort', 'setCategorySort', 'category/' + ajaxify.data.slug); | ||||
|  | ||||
| 		if (!config.usePagination) { | ||||
| 			navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback); | ||||
|   | ||||
| @@ -49,7 +49,7 @@ define('forum/topic', [ | ||||
| 		threadTools.init(tid, $('.topic')); | ||||
| 		events.init(); | ||||
|  | ||||
| 		sort.handleSort('topicPostSort', 'user.setTopicSort', 'topic/' + ajaxify.data.slug); | ||||
| 		sort.handleSort('topicPostSort', 'setTopicSort', 'topic/' + ajaxify.data.slug); | ||||
|  | ||||
| 		if (!config.usePagination) { | ||||
| 			infinitescroll.init($('[component="topic"]'), posts.loadMorePosts); | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| 'use strict'; | ||||
|  | ||||
|  | ||||
| define('sort', ['components'], function (components) { | ||||
| define('sort', ['components', 'api'], function (components, api) { | ||||
| 	var module = {}; | ||||
|  | ||||
| 	module.handleSort = function (field, method, gotoOnSave) { | ||||
| 	module.handleSort = function (field, key, gotoOnSave) { | ||||
| 		var threadSort = components.get('thread/sort'); | ||||
| 		threadSort.find('i').removeClass('fa-check'); | ||||
| 		var currentSetting = threadSort.find('a[data-sort="' + config[field] + '"]'); | ||||
| @@ -20,10 +20,9 @@ define('sort', ['components'], function (components) { | ||||
| 				} | ||||
| 				var newSetting = $(this).attr('data-sort'); | ||||
| 				if (app.user.uid) { | ||||
| 					socket.emit(method, newSetting, function (err) { | ||||
| 						if (err) { | ||||
| 							return app.alertError(err.message); | ||||
| 						} | ||||
| 					api.put(`/users/${app.user.uid}/settings/${key}`, { | ||||
| 						value: newSetting, | ||||
| 					}).then(() => { | ||||
| 						refresh(newSetting, utils.params()); | ||||
| 					}); | ||||
| 				} else { | ||||
|   | ||||
| @@ -89,6 +89,23 @@ usersAPI.deleteMany = async function (caller, data) { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| usersAPI.updateSettings = async function (caller, data) { | ||||
| 	if (!caller.uid || !data || !data.settings) { | ||||
| 		throw new Error('[[error:invalid-data]]'); | ||||
| 	} | ||||
|  | ||||
| 	const canEdit = await privileges.users.canEdit(caller.uid, data.uid); | ||||
| 	if (!canEdit) { | ||||
| 		throw new Error('[[error:no-privileges]]'); | ||||
| 	} | ||||
|  | ||||
| 	return await user.saveSettings(data.uid, data.settings); | ||||
| }; | ||||
|  | ||||
| usersAPI.updateSetting = async function (caller, data) { | ||||
| 	await user.setSetting(data.uid, data.setting, data.value); | ||||
| }; | ||||
|  | ||||
| usersAPI.changePassword = async function (caller, data) { | ||||
| 	await user.changePassword(caller.uid, Object.assign(data, { ip: caller.ip })); | ||||
| 	await events.log({ | ||||
|   | ||||
| @@ -28,6 +28,16 @@ Users.deleteMany = async (req, res) => { | ||||
| 	helpers.formatApiResponse(200, res); | ||||
| }; | ||||
|  | ||||
| Users.updateSettings = async (req, res) => { | ||||
| 	const settings = await api.users.updateSettings(req, { ...req.body, uid: req.params.uid }); | ||||
| 	helpers.formatApiResponse(200, res, settings); | ||||
| }; | ||||
|  | ||||
| Users.updateSetting = async (req, res) => { | ||||
| 	await api.users.updateSetting(req, { ...req.params, value: req.body.value }); | ||||
| 	helpers.formatApiResponse(200, res); | ||||
| }; | ||||
|  | ||||
| Users.changePassword = async (req, res) => { | ||||
| 	await api.users.changePassword(req, { ...req.body, uid: req.params.uid }); | ||||
| 	helpers.formatApiResponse(200, res); | ||||
|   | ||||
| @@ -21,6 +21,9 @@ function authenticatedRoutes() { | ||||
| 	setupApiRoute(router, 'put', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.update); | ||||
| 	setupApiRoute(router, 'delete', '/:uid', [...middlewares, middleware.assert.user, middleware.exposePrivileges], controllers.write.users.delete); | ||||
|  | ||||
| 	setupApiRoute(router, 'put', '/:uid/settings', [...middlewares, middleware.checkRequired.bind(null, ['settings'])], controllers.write.users.updateSettings); | ||||
| 	setupApiRoute(router, 'put', '/:uid/settings/:setting', [...middlewares, middleware.checkRequired.bind(null, ['value'])], controllers.write.users.updateSetting); | ||||
|  | ||||
| 	setupApiRoute(router, 'put', '/:uid/password', [...middlewares, middleware.checkRequired.bind(null, ['newPassword']), middleware.assert.user], controllers.write.users.changePassword); | ||||
|  | ||||
| 	setupApiRoute(router, 'put', '/:uid/follow', [...middlewares, middleware.assert.user], controllers.write.users.follow); | ||||
|   | ||||
| @@ -168,22 +168,27 @@ SocketUser.unfollow = async function (socket, data) { | ||||
| }; | ||||
|  | ||||
| SocketUser.saveSettings = async function (socket, data) { | ||||
| 	if (!socket.uid || !data || !data.settings) { | ||||
| 		throw new Error('[[error:invalid-data]]'); | ||||
| 	} | ||||
| 	const canEdit = await privileges.users.canEdit(socket.uid, data.uid); | ||||
| 	if (!canEdit) { | ||||
| 		throw new Error('[[error:no-privileges]]'); | ||||
| 	} | ||||
| 	return await user.saveSettings(data.uid, data.settings); | ||||
| 	sockets.warnDeprecated(socket, 'PUT /api/v3/users/:uid/settings'); | ||||
| 	const settings = await api.users.updateSettings(socket, data); | ||||
| 	return settings; | ||||
| }; | ||||
|  | ||||
| SocketUser.setTopicSort = async function (socket, sort) { | ||||
| 	await user.setSetting(socket.uid, 'topicPostSort', sort); | ||||
| 	sockets.warnDeprecated(socket, 'PUT /api/v3/users/:uid/setting/topicPostSort'); | ||||
| 	await api.users.updateSetting(socket, { | ||||
| 		uid: socket.uid, | ||||
| 		setting: 'topicPostSort', | ||||
| 		value: sort, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| SocketUser.setCategorySort = async function (socket, sort) { | ||||
| 	await user.setSetting(socket.uid, 'categoryTopicSort', sort); | ||||
| 	sockets.warnDeprecated(socket, 'PUT /api/v3/users/:uid/setting/categoryTopicSort'); | ||||
| 	await api.users.updateSetting(socket, { | ||||
| 		uid: socket.uid, | ||||
| 		setting: 'categoryTopicSort', | ||||
| 		value: sort, | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| SocketUser.getUnreadCount = async function (socket) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user