From 5fe5806514a14ccd7cd3e9adfcae715afb12e25d Mon Sep 17 00:00:00 2001 From: JamesChenX Date: Sun, 15 Sep 2024 15:50:38 +0800 Subject: [PATCH] Support upserting/querying user-defined attributes of users/groups with all client SDKs --- .../turms/client/service/group_service.h | 34 ++++++++++++- .../turms/client/service/user_service.h | 23 +++++++-- .../turms/client/service/group_service.cpp | 16 +++++-- .../src/turms/client/service/user_service.cpp | 10 +++- .../lib/src/service/group_service.dart | 48 +++++++++++++++---- .../lib/src/service/user_service.dart | 35 +++++++++----- turms-client-js/src/service/group-service.ts | 43 +++++++++++++---- turms-client-js/src/service/user-service.ts | 19 ++++++-- .../im/turms/client/service/GroupService.kt | 34 ++++++++++++- .../im/turms/client/service/UserService.kt | 14 +++++- .../TurmsClient/Service/GroupService.swift | 39 +++++++++++++-- .../TurmsClient/Service/UserService.swift | 20 +++++++- 12 files changed, 283 insertions(+), 52 deletions(-) diff --git a/turms-client-cpp/include/turms/client/service/group_service.h b/turms-client-cpp/include/turms/client/service/group_service.h index de84b1a1ed..bd49f4ce0b 100644 --- a/turms-client-cpp/include/turms/client/service/group_service.h +++ b/turms-client-cpp/include/turms/client/service/group_service.h @@ -49,6 +49,8 @@ class GroupService : private boost::noncopyable { using UserInfo = model::proto::UserInfo; using UserInfosWithVersion = model::proto::UserInfosWithVersion; + using Value = model::proto::Value; + public: explicit GroupService(TurmsClient& turmsClient); @@ -84,6 +86,14 @@ class GroupService : private boost::noncopyable { * * If the logged-in user does not have the permission to create the group with typeId, * throws ResponseException with the code * ResponseStatusCode::kNoPermissionToCreateGroupWithGroupType. + * @param userDefinedAttributes the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via + * `turms.service.group.info.user-defined-attributes.allowed-attributes`. Otherwise, the method + * will throw with ResponseStatusCode::kIllegalArgument if + * `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is + * false (false by default), or silently ignored if it is true. + * 2. Only public attributes are supported currently, which means other users can find out these + * attributes via queryGroups(). * @return the group ID. * @throws ResponseException if an error occurs. */ @@ -92,7 +102,8 @@ class GroupService : private boost::noncopyable { const boost::optional& announcement = boost::none, const boost::optional& minScore = boost::none, const boost::optional& muteEndDate = boost::none, - const boost::optional& typeId = boost::none) + const boost::optional& typeId = boost::none, + const std::unordered_map& userDefinedAttributes = {}) -> boost::future>; /** @@ -206,6 +217,24 @@ class GroupService : private boost::noncopyable { * Authorization: * * If the logged-in user is not the owner of the group, * throws ResponseException with the code ResponseStatusCode::kNotGroupOwnerToTransferGroup. + * @param userDefinedAttributes the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via + * `turms.service.group.info.user-defined-attributes.allowed-attributes`. Otherwise, the method + * will throw with ResponseStatusCode::kIllegalArgument if + * `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is + * false (false by default), or silently ignored if it is true. + * 2. If trying to update existing immutable attribute, throws with + * ResponseStatusCode::kIllegalArgument. + * 3. Only public attributes are supported currently, which means other users can find out these + * attributes via queryGroups(). + * + * Authorization: + * * Whether the logged-in user can change the user-defined attributes depends on the group + * type. If not null and the logged-in user does NOT have the permission to change the group + * name, throws ResponseException with the code + * ResponseStatusCode::kNotGroupMemberToUpdateGroupInfo or + * ResponseStatusCode::kNotGroupOwnerOrManagerToUpdateGroupInfo or + * ResponseStatusCode::kNotGroupOwnerToUpdateGroupInfo. * @throws ResponseException if an error occurs. */ auto updateGroup(int64_t groupId, @@ -216,7 +245,8 @@ class GroupService : private boost::noncopyable { const boost::optional& typeId = boost::none, const boost::optional& muteEndDate = boost::none, const boost::optional& successorId = boost::none, - const boost::optional& quitAfterTransfer = boost::none) + const boost::optional& quitAfterTransfer = boost::none, + const std::unordered_map& userDefinedAttributes = {}) -> boost::future>; /** diff --git a/turms-client-cpp/include/turms/client/service/user_service.h b/turms-client-cpp/include/turms/client/service/user_service.h index 26f4b77c4a..e19d650da0 100644 --- a/turms-client-cpp/include/turms/client/service/user_service.h +++ b/turms-client-cpp/include/turms/client/service/user_service.h @@ -189,13 +189,26 @@ class UserService : private boost::noncopyable, private std::enable_shared_from_ * to upload the profile picture and use the returned URL as profilePicture. * @param profileAccessStrategy the new profile access strategy. * If null, the profile access strategy will not be updated. + * @param userDefinedAttributes the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via + * `turms.service.user.info.user-defined-attributes.allowed-attributes`. Otherwise, the method + * will throw with ResponseStatusCode::kIllegalArgument if + * `turms.service.user.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is + * false (false by default), or silently ignored if it is true. + * 2. If trying to update existing immutable attribute, throws with + * ResponseStatusCode::kIllegalArgument. + * 3. Only public attributes are supported currently, which means other users can find out these + * attributes via queryUserProfiles(). + * @param userDefinedAttributes * @throws ResponseException if an error occurs. */ - auto updateProfile(const boost::optional& name = boost::none, - const boost::optional& intro = boost::none, - const boost::optional& profilePicture = boost::none, - const boost::optional& profileAccessStrategy = - boost::none) -> boost::future>; + auto updateProfile( + const boost::optional& name = boost::none, + const boost::optional& intro = boost::none, + const boost::optional& profilePicture = boost::none, + const boost::optional& profileAccessStrategy = boost::none, + const std::unordered_map& userDefinedAttributes = {}) + -> boost::future>; /** * Find user profiles. diff --git a/turms-client-cpp/src/turms/client/service/group_service.cpp b/turms-client-cpp/src/turms/client/service/group_service.cpp index 4e5d7545f0..71bd72330b 100644 --- a/turms-client-cpp/src/turms/client/service/group_service.cpp +++ b/turms-client-cpp/src/turms/client/service/group_service.cpp @@ -14,7 +14,8 @@ auto GroupService::createGroup(const absl::string_view& name, const boost::optional& announcement, const boost::optional& minScore, const boost::optional& muteEndDate, - const boost::optional& typeId) + const boost::optional& typeId, + const std::unordered_map& userDefinedAttributes) -> boost::future> { TurmsRequest turmsRequest; auto* request = turmsRequest.mutable_create_group_request(); @@ -34,6 +35,10 @@ auto GroupService::createGroup(const absl::string_view& name, if (typeId) { request->set_type_id(*typeId); } + if (!userDefinedAttributes.empty()) { + request->mutable_user_defined_attributes()()->insert(userDefinedAttributes.cbegin(), + userDefinedAttributes.cend()); + } return turmsClient_.driver() .send(turmsRequest) .then([](boost::future response) { @@ -62,10 +67,11 @@ auto GroupService::updateGroup(int64_t groupId, const boost::optional& typeId, const boost::optional& muteEndDate, const boost::optional& successorId, - const boost::optional& quitAfterTransfer) + const boost::optional& quitAfterTransfer, + const std::unordered_map& userDefinedAttributes) -> boost::future> { if (!name && !intro && !announcement && !minScore && !typeId && !muteEndDate && !successorId && - !quitAfterTransfer) { + userDefinedAttributes.empty()) { return boost::make_ready_future<>(Response{}); } TurmsRequest turmsRequest; @@ -95,6 +101,10 @@ auto GroupService::updateGroup(int64_t groupId, if (quitAfterTransfer) { request->set_quit_after_transfer(*quitAfterTransfer); } + if (!userDefinedAttributes.empty()) { + request->mutable_user_defined_attributes()()->insert(userDefinedAttributes.cbegin(), + userDefinedAttributes.cend()); + } return turmsClient_.driver() .send(turmsRequest) .then([](boost::future response) { diff --git a/turms-client-cpp/src/turms/client/service/user_service.cpp b/turms-client-cpp/src/turms/client/service/user_service.cpp index 1529830b07..5c0df873db 100644 --- a/turms-client-cpp/src/turms/client/service/user_service.cpp +++ b/turms-client-cpp/src/turms/client/service/user_service.cpp @@ -211,9 +211,11 @@ auto UserService::updatePassword(const std::string& password) -> boost::future& name, const boost::optional& intro, const boost::optional& profilePicture, - const boost::optional& profileAccessStrategy) + const boost::optional& profileAccessStrategy, + const std::unordered_map& userDefinedAttributes) -> boost::future> { - if (!name && !intro && !profileAccessStrategy) { + if (!name && !intro && !profilePicture && !profileAccessStrategy && + userDefinedAttributes.empty()) { return boost::make_ready_future<>(Response{}); } TurmsRequest turmsRequest; @@ -230,6 +232,10 @@ auto UserService::updateProfile(const boost::optional& name, if (profileAccessStrategy) { request->set_profile_access_strategy(*profileAccessStrategy); } + if (!userDefinedAttributes.empty()) { + request->mutable_user_defined_attributes()->insert(userDefinedAttributes.cbegin(), + userDefinedAttributes.cend()); + } return turmsClient_.driver() .send(turmsRequest) .then([](boost::future response) { diff --git a/turms-client-dart/lib/src/service/group_service.dart b/turms-client-dart/lib/src/service/group_service.dart index b282e66b71..4359dbaf20 100644 --- a/turms-client-dart/lib/src/service/group_service.dart +++ b/turms-client-dart/lib/src/service/group_service.dart @@ -2,8 +2,6 @@ import 'package:fixnum/fixnum.dart' show Int64; import '../../turms_client.dart'; import '../extension/notification_extensions.dart'; -import '../model/proto/request/group/enrollment/update_group_invitation_request.pb.dart'; -import '../model/proto/request/group/enrollment/update_group_join_request_request.pb.dart'; class GroupService { final TurmsClient _turmsClient; @@ -38,6 +36,13 @@ class GroupService { /// throws [ResponseException] with the code [ResponseStatusCode.createGroupWithNonexistentGroupType]. /// * If the logged-in user does not have the permission to create the group with [typeId], /// throws [ResponseException] with the code [ResponseStatusCode.noPermissionToCreateGroupWithGroupType]. + /// * `userDefinedAttributes`: The user-defined attributes for upsert. + /// 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + /// Otherwise, the method will throw with [ResponseStatusCode.illegalArgument] + /// if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + /// or silently ignored if it is true. + /// 2. Only public attributes are supported currently, which means other users can find out these attributes + /// via [queryGroups]. /// /// **Returns**: The group ID. /// @@ -47,14 +52,16 @@ class GroupService { String? announcement, int? minScore, DateTime? muteEndDate, - Int64? typeId}) async { + Int64? typeId, + Map? userDefinedAttributes}) async { final n = await _turmsClient.driver.send(CreateGroupRequest( name: name, intro: intro, announcement: announcement, minScore: minScore, muteEndDate: muteEndDate?.toInt64(), - typeId: typeId)); + typeId: typeId, + userDefinedAttributes: userDefinedAttributes)); return n.toResponse((data) => data.getLongOrThrow()); } @@ -161,6 +168,21 @@ class GroupService { /// Authorization: /// * If the logged-in user is not the owner of the group, /// throws [ResponseException] with the code [ResponseStatusCode.notGroupOwnerToTransferGroup]. + /// * `userDefinedAttributes`: The user-defined attributes for upsert. + /// 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + /// Otherwise, the method will throw with [ResponseStatusCode.illegalArgument] + /// if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + /// or silently ignored if it is true. + /// 2. If trying to update existing immutable attribute, throws with [ResponseStatusCode.illegalArgument]. + /// 3. Only public attributes are supported currently, which means other users can find out these attributes + /// via [queryGroups]. + /// + /// Authorization: + /// * Whether the logged-in user can change the user-defined attributes depends on the group type. + /// If not null and the logged-in user does NOT have the permission to change the group name, + /// throws [ResponseException] with the code [ResponseStatusCode.notGroupMemberToUpdateGroupInfo] + /// or [ResponseStatusCode.notGroupOwnerOrManagerToUpdateGroupInfo] + /// or [ResponseStatusCode.notGroupOwnerToUpdateGroupInfo]. /// /// **Throws**: [ResponseException] if an error occurs. Future> updateGroup(Int64 groupId, @@ -171,9 +193,18 @@ class GroupService { Int64? typeId, DateTime? muteEndDate, Int64? successorId, - bool? quitAfterTransfer}) async { - if ([name, intro, announcement, minScore, typeId, muteEndDate, successorId] - .areAllNull) { + bool? quitAfterTransfer, + Map? userDefinedAttributes}) async { + if ([ + name, + intro, + announcement, + minScore, + typeId, + muteEndDate, + successorId, + userDefinedAttributes + ].areAllNullOrEmpty) { return Response.nullValue(); } final n = await _turmsClient.driver.send(UpdateGroupRequest( @@ -185,7 +216,8 @@ class GroupService { minScore: minScore, typeId: typeId, successorId: successorId, - quitAfterTransfer: quitAfterTransfer)); + quitAfterTransfer: quitAfterTransfer, + userDefinedAttributes: userDefinedAttributes)); return n.toNullResponse(); } diff --git a/turms-client-dart/lib/src/service/user_service.dart b/turms-client-dart/lib/src/service/user_service.dart index 32d6dc54c1..f479c7a2d8 100644 --- a/turms-client-dart/lib/src/service/user_service.dart +++ b/turms-client-dart/lib/src/service/user_service.dart @@ -2,12 +2,6 @@ import 'package:fixnum/fixnum.dart'; import '../../turms_client.dart'; import '../extension/notification_extensions.dart'; -import '../model/proto/model/common/value.pb.dart'; -import '../model/proto/model/user/user_settings.pb.dart'; -import '../model/proto/request/user/delete_user_settings_request.pb.dart'; -import '../model/proto/request/user/query_user_settings_request.pb.dart'; -import '../model/proto/request/user/relationship/delete_friend_request_request.pb.dart'; -import '../model/proto/request/user/update_user_settings_request.pb.dart'; import '../util/system.dart'; class Location { @@ -237,21 +231,38 @@ class UserService { /// to upload the profile picture and use the returned URL as [profilePicture]. /// * `profileAccessStrategy`: The new profile access strategy. /// If null, the profile access strategy will not be updated. + /// * `userDefinedAttributes`: The user-defined attributes for upsert. + /// 1. The attributes must have been defined on the server side via `turms.service.user.info.user-defined-attributes.allowed-attributes`. + /// Otherwise, the method will throw with [ResponseStatusCode.illegalArgument] + /// if `turms.service.user.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + /// or silently ignored if it is true. + /// 2. If trying to update existing immutable attribute, throws with [ResponseStatusCode.illegalArgument]. + /// 3. Only public attributes are supported currently, which means other users can find out these attributes + /// via [queryUserProfiles]. /// /// **Throws**: [ResponseException] if an error occurs. Future> updateProfile( {String? name, String? intro, String? profilePicture, - ProfileAccessStrategy? profileAccessStrategy}) async { - if ([name, intro, profilePicture, profileAccessStrategy].areAllNull) { + ProfileAccessStrategy? profileAccessStrategy, + Map? userDefinedAttributes}) async { + if ([ + name, + intro, + profilePicture, + profileAccessStrategy, + userDefinedAttributes + ].areAllNullOrEmpty) { return Response.nullValue(); } final n = await _turmsClient.driver.send(UpdateUserRequest( - name: name, - intro: intro, - profilePicture: profilePicture, - profileAccessStrategy: profileAccessStrategy)); + name: name, + intro: intro, + profilePicture: profilePicture, + profileAccessStrategy: profileAccessStrategy, + userDefinedAttributes: userDefinedAttributes, + )); return n.toNullResponse(); } diff --git a/turms-client-js/src/service/group-service.ts b/turms-client-js/src/service/group-service.ts index 56ccaff0c1..2013919da4 100644 --- a/turms-client-js/src/service/group-service.ts +++ b/turms-client-js/src/service/group-service.ts @@ -11,6 +11,7 @@ import Validator from '../util/validator'; import CollectionUtil from '../util/collection-util'; import NewGroupJoinQuestion from '../model/new-group-join-question'; import { ResponseAction } from '../model/proto/constant/response_action'; +import { Value } from '../model/proto/model/common/value'; export default class GroupService { private _turmsClient: TurmsClient; @@ -48,6 +49,13 @@ export default class GroupService { * throws {@link {@link ResponseError}} with the code {@link ResponseStatusCode#CREATE_GROUP_WITH_NONEXISTENT_GROUP_TYPE}. * * If the logged-in user does not have the permission to create the group with {@link typeId}, * throws {@link {@link ResponseError}} with the code {@link ResponseStatusCode#NO_PERMISSION_TO_CREATE_GROUP_WITH_GROUP_TYPE}. + * @param userDefinedAttributes - the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + * Otherwise, the method will throw with {@link ResponseStatusCode#ILLEGAL_ARGUMENT} + * if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + * or silently ignored if it is true. + * 2. Only public attributes are supported currently, which means other users can find out these attributes + * via {@link queryGroups}. * @returns the group ID. * @throws {@link ResponseError} if an error occurs. */ @@ -57,14 +65,16 @@ export default class GroupService { announcement, minScore, muteEndDate, - typeId + typeId, + userDefinedAttributes }: { name: string, intro?: string, announcement?: string, minScore?: number, muteEndDate?: Date, - typeId?: string + typeId?: string, + userDefinedAttributes?: Record }): Promise> { if (null == name) { return ResponseError.notNullPromise('name'); @@ -77,7 +87,7 @@ export default class GroupService { minScore, muteEndDate: DataParser.getDateTimeStr(muteEndDate), typeId, - userDefinedAttributes: {}, + userDefinedAttributes, customAttributes: [] }, customAttributes: [] @@ -201,6 +211,21 @@ export default class GroupService { * Authorization: * * If the logged-in user is not the owner of the group, * throws {@link {@link ResponseError}} with the code {@link ResponseStatusCode#NOT_GROUP_OWNER_TO_TRANSFER_GROUP}. + * @param userDefinedAttributes - the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + * Otherwise, the method will throw with {@link ResponseStatusCode#ILLEGAL_ARGUMENT} + * if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + * or silently ignored if it is true. + * 2. If trying to update existing immutable attribute, throws with {@link ResponseStatusCode#ILLEGAL_ARGUMENT}. + * 3. Only public attributes are supported currently, which means other users can find out these attributes + * via {@link queryGroups}. + * + * Authorization: + * * Whether the logged-in user can change the user-defined attributes depends on the group type. + * If not null and the logged-in user does NOT have the permission to change the group name, + * throws {@link {@link ResponseError}} with the code {@link ResponseStatusCode#NOT_GROUP_MEMBER_TO_UPDATE_GROUP_INFO} + * or {@link ResponseStatusCode#NOT_GROUP_OWNER_OR_MANAGER_TO_UPDATE_GROUP_INFO} + * or {@link ResponseStatusCode#NOT_GROUP_OWNER_TO_UPDATE_GROUP_INFO}. * @throws {@link ResponseError} if an error occurs. */ updateGroup({ @@ -212,7 +237,8 @@ export default class GroupService { typeId, muteEndDate, successorId, - quitAfterTransfer + quitAfterTransfer, + userDefinedAttributes }: { groupId: string, name?: string, @@ -222,13 +248,14 @@ export default class GroupService { typeId?: string, muteEndDate?: Date, successorId?: string, - quitAfterTransfer?: boolean + quitAfterTransfer?: boolean, + userDefinedAttributes?: Record }): Promise> { if (null == groupId) { return ResponseError.notNullPromise('groupId'); } - if (Validator.areAllNull(name, intro, announcement, minScore, typeId, - muteEndDate, successorId)) { + if (Validator.areAllNullOrEmpty(name, intro, announcement, minScore, typeId, + muteEndDate, successorId, userDefinedAttributes)) { return Promise.resolve(Response.nullValue()); } return this._turmsClient.driver.send({ @@ -242,7 +269,7 @@ export default class GroupService { typeId, successorId, quitAfterTransfer, - userDefinedAttributes: {}, + userDefinedAttributes, customAttributes: [] }, customAttributes: [] diff --git a/turms-client-js/src/service/user-service.ts b/turms-client-js/src/service/user-service.ts index a243ed0e54..fcf20c0db8 100644 --- a/turms-client-js/src/service/user-service.ts +++ b/turms-client-js/src/service/user-service.ts @@ -17,6 +17,7 @@ import UserLocation from '../model/user-location'; import { UserStatus } from '../model/proto/constant/user_status'; import Validator from '../util/validator'; import CollectionUtil from '../util/collection-util'; +import { Value } from '../model/proto/model/common/value'; const INVALID_ONLINE_STATUSES = [UserStatus.OFFLINE]; @@ -459,20 +460,30 @@ export default class UserService { * to upload the profile picture and use the returned URL as {@link profilePicture}. * @param profileAccessStrategy - the new profile access strategy. * If null, the profile access strategy will not be updated. + * @param userDefinedAttributes - the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via `turms.service.user.info.user-defined-attributes.allowed-attributes`. + * Otherwise, the method will throw with {@link ResponseStatusCode#ILLEGAL_ARGUMENT} + * if `turms.service.user.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + * or silently ignored if it is true. + * 2. If trying to update existing immutable attribute, throws with {@link ResponseStatusCode#ILLEGAL_ARGUMENT}. + * 3. Only public attributes are supported currently, which means other users can find out these attributes + * via {@link queryUserProfiles}. * @throws {@link ResponseError} if an error occurs. */ updateProfile({ name, intro, profilePicture, - profileAccessStrategy + profileAccessStrategy, + userDefinedAttributes }: { name?: string, intro?: string, profilePicture?: string, - profileAccessStrategy?: string | ProfileAccessStrategy + profileAccessStrategy?: string | ProfileAccessStrategy, + userDefinedAttributes?: Record }): Promise> { - if (Validator.areAllNull(name, intro, profileAccessStrategy)) { + if (Validator.areAllNullOrEmpty(name, intro, profilePicture, profileAccessStrategy, userDefinedAttributes)) { return Promise.resolve(Response.nullValue()); } if (null != profileAccessStrategy) { @@ -487,7 +498,7 @@ export default class UserService { intro, profilePicture, profileAccessStrategy: profileAccessStrategy as ProfileAccessStrategy | undefined, - userDefinedAttributes: {}, + userDefinedAttributes, customAttributes: [] }, customAttributes: [] diff --git a/turms-client-kotlin/src/main/kotlin/im/turms/client/service/GroupService.kt b/turms-client-kotlin/src/main/kotlin/im/turms/client/service/GroupService.kt index 73a800f4f5..c61aff71e1 100644 --- a/turms-client-kotlin/src/main/kotlin/im/turms/client/service/GroupService.kt +++ b/turms-client-kotlin/src/main/kotlin/im/turms/client/service/GroupService.kt @@ -27,6 +27,7 @@ import im.turms.client.model.ResponseStatusCode import im.turms.client.model.proto.constant.GroupMemberRole import im.turms.client.model.proto.constant.ResponseAction import im.turms.client.model.proto.model.common.LongsWithVersion +import im.turms.client.model.proto.model.common.Value import im.turms.client.model.proto.model.group.Group import im.turms.client.model.proto.model.group.GroupInvitationsWithVersion import im.turms.client.model.proto.model.group.GroupJoinQuestion @@ -98,6 +99,13 @@ class GroupService(private val turmsClient: TurmsClient) { * throws [ResponseException] with the code [ResponseStatusCode.CREATE_GROUP_WITH_NONEXISTENT_GROUP_TYPE]. * * If the logged-in user does not have the permission to create the group with [typeId], * throws [ResponseException] with the code [ResponseStatusCode.NO_PERMISSION_TO_CREATE_GROUP_WITH_GROUP_TYPE]. + * @param userDefinedAttributes the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + * Otherwise, the method will throw with [ResponseStatusCode.ILLEGAL_ARGUMENT] + * if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + * or silently ignored if it is true. + * 2. Only public attributes are supported currently, which means other users can find out these attributes + * via [queryGroups]. * @return the group ID. * @throws ResponseException if an error occurs. */ @@ -108,6 +116,7 @@ class GroupService(private val turmsClient: TurmsClient) { minScore: Int? = null, muteEndDate: Date? = null, typeId: Long? = null, + userDefinedAttributes: Map? = null, ): Response = turmsClient.driver .send( @@ -118,6 +127,9 @@ class GroupService(private val turmsClient: TurmsClient) { minScore?.let { this.minScore = it } muteEndDate?.let { this.muteEndDate = it.time } typeId?.let { this.typeId = it } + userDefinedAttributes?.takeIf { it.isNotEmpty() }?.let { + putAllUserDefinedAttributes(it) + } }, ).toResponse { it.getLongOrThrow() @@ -231,6 +243,21 @@ class GroupService(private val turmsClient: TurmsClient) { * Authorization: * * If the logged-in user is not the owner of the group, * throws [ResponseException] with the code [ResponseStatusCode.NOT_GROUP_OWNER_TO_TRANSFER_GROUP]. + * @param userDefinedAttributes the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + * Otherwise, the method will throw with [ResponseStatusCode.ILLEGAL_ARGUMENT] + * if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + * or silently ignored if it is true. + * 2. If trying to update existing immutable attribute, throws with [ResponseStatusCode.ILLEGAL_ARGUMENT]. + * 3. Only public attributes are supported currently, which means other users can find out these attributes + * via [queryGroups]. + * + * Authorization: + * * Whether the logged-in user can change the user-defined attributes depends on the group type. + * If not null and the logged-in user does NOT have the permission to change the group name, + * throws [ResponseException] with the code [ResponseStatusCode.NOT_GROUP_MEMBER_TO_UPDATE_GROUP_INFO] + * or [ResponseStatusCode.NOT_GROUP_OWNER_OR_MANAGER_TO_UPDATE_GROUP_INFO] + * or [ResponseStatusCode.NOT_GROUP_OWNER_TO_UPDATE_GROUP_INFO]. * @throws ResponseException if an error occurs. */ suspend fun updateGroup( @@ -243,8 +270,9 @@ class GroupService(private val turmsClient: TurmsClient) { muteEndDate: Date? = null, successorId: Long? = null, quitAfterTransfer: Boolean? = null, + userDefinedAttributes: Map? = null, ): Response = - if (Validator.areAllNull( + if (Validator.areAllNullOrEmpty( name, intro, announcement, @@ -252,6 +280,7 @@ class GroupService(private val turmsClient: TurmsClient) { typeId, muteEndDate, successorId, + userDefinedAttributes, ) ) { Response.unitValue() @@ -268,6 +297,9 @@ class GroupService(private val turmsClient: TurmsClient) { typeId?.let { this.typeId = it } successorId?.let { this.successorId = it } quitAfterTransfer?.let { this.quitAfterTransfer = it } + userDefinedAttributes?.takeIf { it.isNotEmpty() }?.let { + putAllUserDefinedAttributes(it) + } }, ) .toResponse() diff --git a/turms-client-kotlin/src/main/kotlin/im/turms/client/service/UserService.kt b/turms-client-kotlin/src/main/kotlin/im/turms/client/service/UserService.kt index 3942412021..a046732965 100644 --- a/turms-client-kotlin/src/main/kotlin/im/turms/client/service/UserService.kt +++ b/turms-client-kotlin/src/main/kotlin/im/turms/client/service/UserService.kt @@ -323,6 +323,14 @@ class UserService(private val turmsClient: TurmsClient) { * to upload the profile picture and use the returned URL as [profilePicture]. * @param profileAccessStrategy the new profile access strategy. * If null, the profile access strategy will not be updated. + * @param userDefinedAttributes the user-defined attributes for upsert. + * 1. The attributes must have been defined on the server side via `turms.service.user.info.user-defined-attributes.allowed-attributes`. + * Otherwise, the method will throw with [ResponseStatusCode.ILLEGAL_ARGUMENT] + * if `turms.service.user.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + * or silently ignored if it is true. + * 2. If trying to update existing immutable attribute, throws with [ResponseStatusCode.ILLEGAL_ARGUMENT]. + * 3. Only public attributes are supported currently, which means other users can find out these attributes + * via [queryUserProfiles]. * @throws ResponseException if an error occurs. */ suspend fun updateProfile( @@ -330,8 +338,9 @@ class UserService(private val turmsClient: TurmsClient) { intro: String? = null, profilePicture: String? = null, profileAccessStrategy: ProfileAccessStrategy? = null, + userDefinedAttributes: Map? = null, ): Response = - if (Validator.areAllNull(name, intro, profileAccessStrategy)) { + if (Validator.areAllNullOrEmpty(name, intro, profilePicture, profileAccessStrategy, userDefinedAttributes)) { Response.unitValue() } else { turmsClient.driver @@ -341,6 +350,9 @@ class UserService(private val turmsClient: TurmsClient) { intro?.let { this.intro = it } profilePicture?.let { this.profilePicture = it } profileAccessStrategy?.let { this.profileAccessStrategy = it } + userDefinedAttributes?.takeIf { it.isNotEmpty() }?.let { + this.putAllUserDefinedAttributes(it) + } }, ) .toResponse() diff --git a/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift b/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift index e8977ec6c1..123460bea0 100755 --- a/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift @@ -35,6 +35,13 @@ public class GroupService { /// throws ``ResponseError`` with the code ``ResponseStatusCode/createGroupWithNonexistentGroupType``. /// * If the logged-in user does not have the permission to create the group with `typeId`, /// throws ``ResponseError`` with the code ``ResponseStatusCode/noPermissionToCreateGroupWithGroupType``. + /// - userDefinedAttributes: The user-defined attributes for upsert. + /// 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + /// Otherwise, the method will throw with ``ResponseStatusCode/illegalArgument`` + /// if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + /// or silently ignored if it is true. + /// 2. Only public attributes are supported currently, which means other users can find out these attributes + /// via ``queryGroups``. /// /// - Returns: The group ID. /// @@ -45,7 +52,8 @@ public class GroupService { announcement: String? = nil, minScore: Int32? = nil, muteEndDate: Date? = nil, - typeId: Int64? = nil + typeId: Int64? = nil, + userDefinedAttributes: [String: Value]? = nil ) async throws -> Response { return try (await turmsClient.driver .send { @@ -66,6 +74,9 @@ public class GroupService { if let typeId { $0.typeID = typeId } + if let userDefinedAttributes { + $0.userDefinedAttributes = userDefinedAttributes + } } }).toResponse { try $0.getLongOrThrow() @@ -178,6 +189,21 @@ public class GroupService { /// Authorization: /// * If the logged-in user is not the owner of the group, /// throws ``ResponseError`` with the code ``ResponseStatusCode/notGroupOwnerToTransferGroup``. + /// - userDefinedAttributes: The user-defined attributes for upsert. + /// 1. The attributes must have been defined on the server side via `turms.service.group.info.user-defined-attributes.allowed-attributes`. + /// Otherwise, the method will throw with ``ResponseStatusCode/illegalArgument`` + /// if `turms.service.group.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + /// or silently ignored if it is true. + /// 2. If trying to update existing immutable attribute, throws with ``ResponseStatusCode/illegalArgument``. + /// 3. Only public attributes are supported currently, which means other users can find out these attributes + /// via ``queryGroups``. + /// + /// Authorization: + /// * Whether the logged-in user can change the user-defined attributes depends on the group type. + /// If not nil and the logged-in user does NOT have the permission to change the group name, + /// throws ``ResponseError`` with the code ``ResponseStatusCode/notGroupMemberToUpdateGroupInfo`` + /// or ``ResponseStatusCode/notGroupOwnerOrManagerToUpdateGroupInfo`` + /// or ``ResponseStatusCode/notGroupOwnerToUpdateGroupInfo``. /// /// - Throws: ``ResponseError`` if an error occurs. public func updateGroup( @@ -189,16 +215,18 @@ public class GroupService { typeId: Int64? = nil, muteEndDate: Date? = nil, successorId: Int64? = nil, - quitAfterTransfer: Bool? = nil + quitAfterTransfer: Bool? = nil, + userDefinedAttributes: [String: Value]? = nil ) async throws -> Response { - if Validator.areAllNil( + if Validator.areAllNilOrEmpty( name, intro, announcement, minScore, typeId, muteEndDate, - successorId + successorId, + userDefinedAttributes ) { return Response.empty() } @@ -230,6 +258,9 @@ public class GroupService { if let quitAfterTransfer { $0.quitAfterTransfer = quitAfterTransfer } + if let userDefinedAttributes { + $0.userDefinedAttributes = userDefinedAttributes + } } }).toResponse() } diff --git a/turms-client-swift/Sources/TurmsClient/Service/UserService.swift b/turms-client-swift/Sources/TurmsClient/Service/UserService.swift index d3466a03eb..a3ef009f9e 100755 --- a/turms-client-swift/Sources/TurmsClient/Service/UserService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/UserService.swift @@ -232,14 +232,24 @@ public class UserService { /// to upload the profile picture and use the returned URL as `profilePicture`. /// - profileAccessStrategy: The new profile access strategy. /// If nil, the profile access strategy will not be updated. + /// - userDefinedAttributes: The user-defined attributes for upsert. + /// 1. The attributes must have been defined on the server side via `turms.service.user.info.user-defined-attributes.allowed-attributes`. + /// Otherwise, the method will throw with ``ResponseStatusCode/illegalArgument`` + /// if `turms.service.user.info.user-defined-attributes.ignore-unknown-attributes-on-upsert` is false (false by default), + /// or silently ignored if it is true. + /// 2. If trying to update existing immutable attribute, throws with ``ResponseStatusCode/illegalArgument``. + /// 3. Only public attributes are supported currently, which means other users can find out these attributes + /// via ``queryUserProfiles``. /// /// - Throws: ``ResponseError`` if an error occurs. public func updateProfile( name: String? = nil, intro: String? = nil, - profileAccessStrategy: ProfileAccessStrategy? = nil + profilePicture: String? = nil, + profileAccessStrategy: ProfileAccessStrategy? = nil, + userDefinedAttributes: [String: Value]? = nil ) async throws -> Response { - if Validator.areAllNil(name, intro, profileAccessStrategy) { + if Validator.areAllNilOrEmpty(name, intro, profilePicture, profileAccessStrategy, userDefinedAttributes) { return Response.empty() } return try (await turmsClient.driver @@ -251,9 +261,15 @@ public class UserService { if let intro { $0.intro = intro } + if let profilePicture { + $0.profilePicture = profilePicture + } if let profileAccessStrategy { $0.profileAccessStrategy = profileAccessStrategy } + if let userDefinedAttributes { + $0.userDefinedAttributes = userDefinedAttributes + } } } ).toResponse()