diff --git a/api/v2/user_setting_service.go b/api/v2/user_setting_service.go new file mode 100644 index 0000000..86e3166 --- /dev/null +++ b/api/v2/user_setting_service.go @@ -0,0 +1,94 @@ +package v2 + +import ( + "context" + + apiv2pb "github.com/boojack/slash/proto/gen/api/v2" + storepb "github.com/boojack/slash/proto/gen/store" + "github.com/boojack/slash/store" + "github.com/pkg/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type UserSettingService struct { + apiv2pb.UnimplementedUserSettingServiceServer + + Store *store.Store +} + +// NewUserSettingService creates a new UserSettingService. +func NewUserSettingService(store *store.Store) *UserSettingService { + return &UserSettingService{ + Store: store, + } +} + +func (s *UserSettingService) GetUserSetting(ctx context.Context, request *apiv2pb.GetUserSettingRequest) (*apiv2pb.GetUserSettingResponse, error) { + userSetting, err := getUserSetting(ctx, s.Store, request.Id) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get user setting: %v", err) + } + return &apiv2pb.GetUserSettingResponse{ + UserSetting: userSetting, + }, nil +} + +func (s *UserSettingService) UpdateUserSetting(ctx context.Context, request *apiv2pb.UpdateUserSettingRequest) (*apiv2pb.UpdateUserSettingResponse, error) { + if len(request.UpdateMask.Paths) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "update mask is empty") + } + + userID := ctx.Value(UserIDContextKey).(int32) + for _, path := range request.UpdateMask.Paths { + if path == "locale" { + if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{ + UserId: userID, + Key: storepb.UserSettingKey_USER_SETTING_LOCALE, + Value: &storepb.UserSetting_Locale{ + Locale: convertLocaleStringToStore(request.UserSetting.Locale), + }, + }); err != nil { + return nil, status.Errorf(codes.Internal, "failed to update user setting: %v", err) + } + } else { + return nil, status.Errorf(codes.InvalidArgument, "invalid path: %s", path) + } + } + + userSetting, err := getUserSetting(ctx, s.Store, request.Id) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get user setting: %v", err) + } + return &apiv2pb.UpdateUserSettingResponse{ + UserSetting: userSetting, + }, nil +} + +func getUserSetting(ctx context.Context, s *store.Store, userID int32) (*apiv2pb.UserSetting, error) { + userSettings, err := s.ListUserSettings(ctx, &store.FindUserSetting{ + UserID: &userID, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to find user setting") + } + + userSetting := &apiv2pb.UserSetting{} + for _, setting := range userSettings { + if setting.Key == storepb.UserSettingKey_USER_SETTING_LOCALE { + userSetting.Locale = setting.GetLocale().String() + } + } + return userSetting, nil +} + +func convertLocaleStringToStore(locale string) storepb.LocaleUserSetting { + switch locale { + case "en": + return storepb.LocaleUserSetting_LOCALE_USER_SETTING_EN + case "zh": + return storepb.LocaleUserSetting_LOCALE_USER_SETTING_ZH + default: + return storepb.LocaleUserSetting_LOCALE_USER_SETTING_UNSPECIFIED + } +} diff --git a/frontend/types/proto/api/v2/user_setting_service_pb.d.ts b/frontend/types/proto/api/v2/user_setting_service_pb.d.ts index b74fb3b..23fcea3 100644 --- a/frontend/types/proto/api/v2/user_setting_service_pb.d.ts +++ b/frontend/types/proto/api/v2/user_setting_service_pb.d.ts @@ -3,7 +3,7 @@ /* eslint-disable */ // @ts-nocheck -import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import type { BinaryReadOptions, FieldList, FieldMask, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3 } from "@bufbuild/protobuf"; /** @@ -101,10 +101,19 @@ export declare class UpdateUserSettingRequest extends Message); static readonly runtime: typeof proto3; diff --git a/frontend/types/proto/api/v2/user_setting_service_pb.js b/frontend/types/proto/api/v2/user_setting_service_pb.js index b7faf22..d06a862 100644 --- a/frontend/types/proto/api/v2/user_setting_service_pb.js +++ b/frontend/types/proto/api/v2/user_setting_service_pb.js @@ -3,7 +3,7 @@ /* eslint-disable */ // @ts-nocheck -import { proto3 } from "@bufbuild/protobuf"; +import { FieldMask, proto3 } from "@bufbuild/protobuf"; /** * @generated from message slash.api.v2.UserSetting @@ -44,6 +44,7 @@ export const UpdateUserSettingRequest = proto3.makeMessageType( () => [ { no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, { no: 2, name: "user_setting", kind: "message", T: UserSetting }, + { no: 3, name: "update_mask", kind: "message", T: FieldMask }, ], ); diff --git a/proto/api/v2/user_setting_service.proto b/proto/api/v2/user_setting_service.proto index a672b81..39c4ae8 100644 --- a/proto/api/v2/user_setting_service.proto +++ b/proto/api/v2/user_setting_service.proto @@ -4,6 +4,7 @@ package slash.api.v2; import "google/api/annotations.proto"; import "google/api/client.proto"; +import "google/protobuf/field_mask.proto"; option go_package = "gen/api/v2"; @@ -44,7 +45,11 @@ message UpdateUserSettingRequest { // id is the user id. int32 id = 1; + // user_setting is the user setting to update. UserSetting user_setting = 2; + + // update_mask is the field mask to update the user setting. + google.protobuf.FieldMask update_mask = 3; } message UpdateUserSettingResponse { diff --git a/proto/gen/api/v2/README.md b/proto/gen/api/v2/README.md index 65a3001..8a77351 100644 --- a/proto/gen/api/v2/README.md +++ b/proto/gen/api/v2/README.md @@ -610,7 +610,8 @@ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [int32](#int32) | | id is the user id. | -| user_setting | [UserSetting](#slash-api-v2-UserSetting) | | | +| user_setting | [UserSetting](#slash-api-v2-UserSetting) | | user_setting is the user setting to update. | +| update_mask | [google.protobuf.FieldMask](#google-protobuf-FieldMask) | | update_mask is the field mask to update the user setting. | diff --git a/proto/gen/api/v2/user_setting_service.pb.go b/proto/gen/api/v2/user_setting_service.pb.go index d5a4406..c3a650d 100644 --- a/proto/gen/api/v2/user_setting_service.pb.go +++ b/proto/gen/api/v2/user_setting_service.pb.go @@ -10,6 +10,7 @@ import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" reflect "reflect" sync "sync" ) @@ -179,8 +180,11 @@ type UpdateUserSettingRequest struct { unknownFields protoimpl.UnknownFields // id is the user id. - Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // user_setting is the user setting to update. UserSetting *UserSetting `protobuf:"bytes,2,opt,name=user_setting,json=userSetting,proto3" json:"user_setting,omitempty"` + // update_mask is the field mask to update the user setting. + UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"` } func (x *UpdateUserSettingRequest) Reset() { @@ -229,6 +233,13 @@ func (x *UpdateUserSettingRequest) GetUserSetting() *UserSetting { return nil } +func (x *UpdateUserSettingRequest) GetUpdateMask() *fieldmaskpb.FieldMask { + if x != nil { + return x.UpdateMask + } + return nil +} + type UpdateUserSettingResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -285,25 +296,31 @@ var file_api_v2_user_setting_service_proto_rawDesc = []byte{ 0x32, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, - 0x27, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x22, 0x68, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3c, 0x0a, 0x0c, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x75, - 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x59, 0x0a, 0x19, 0x55, 0x70, + 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, + 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x0b, 0x55, 0x73, + 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x65, 0x22, 0x27, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x6c, 0x61, + 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x22, 0xa5, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x3c, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3b, 0x0a, + 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x59, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, @@ -361,20 +378,22 @@ var file_api_v2_user_setting_service_proto_goTypes = []interface{}{ (*GetUserSettingResponse)(nil), // 2: slash.api.v2.GetUserSettingResponse (*UpdateUserSettingRequest)(nil), // 3: slash.api.v2.UpdateUserSettingRequest (*UpdateUserSettingResponse)(nil), // 4: slash.api.v2.UpdateUserSettingResponse + (*fieldmaskpb.FieldMask)(nil), // 5: google.protobuf.FieldMask } var file_api_v2_user_setting_service_proto_depIdxs = []int32{ 0, // 0: slash.api.v2.GetUserSettingResponse.user_setting:type_name -> slash.api.v2.UserSetting 0, // 1: slash.api.v2.UpdateUserSettingRequest.user_setting:type_name -> slash.api.v2.UserSetting - 0, // 2: slash.api.v2.UpdateUserSettingResponse.user_setting:type_name -> slash.api.v2.UserSetting - 1, // 3: slash.api.v2.UserSettingService.GetUserSetting:input_type -> slash.api.v2.GetUserSettingRequest - 3, // 4: slash.api.v2.UserSettingService.UpdateUserSetting:input_type -> slash.api.v2.UpdateUserSettingRequest - 2, // 5: slash.api.v2.UserSettingService.GetUserSetting:output_type -> slash.api.v2.GetUserSettingResponse - 4, // 6: slash.api.v2.UserSettingService.UpdateUserSetting:output_type -> slash.api.v2.UpdateUserSettingResponse - 5, // [5:7] is the sub-list for method output_type - 3, // [3:5] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 5, // 2: slash.api.v2.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask + 0, // 3: slash.api.v2.UpdateUserSettingResponse.user_setting:type_name -> slash.api.v2.UserSetting + 1, // 4: slash.api.v2.UserSettingService.GetUserSetting:input_type -> slash.api.v2.GetUserSettingRequest + 3, // 5: slash.api.v2.UserSettingService.UpdateUserSetting:input_type -> slash.api.v2.UpdateUserSettingRequest + 2, // 6: slash.api.v2.UserSettingService.GetUserSetting:output_type -> slash.api.v2.GetUserSettingResponse + 4, // 7: slash.api.v2.UserSettingService.UpdateUserSetting:output_type -> slash.api.v2.UpdateUserSettingResponse + 6, // [6:8] is the sub-list for method output_type + 4, // [4:6] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_api_v2_user_setting_service_proto_init() }