批量删除

This commit is contained in:
zhx 2026-07-05 14:55:37 +08:00
parent 6c3841248e
commit 15810e891d
17 changed files with 5300 additions and 4114 deletions

File diff suppressed because it is too large Load Diff

View File

@ -672,6 +672,24 @@ message SetResourceStatusRequest {
int64 operator_user_id = 5; int64 operator_user_id = 5;
} }
message DeleteResourceRequest {
string request_id = 1;
string app_code = 2;
int64 resource_id = 3;
int64 operator_user_id = 4;
}
message BatchDeleteResourcesRequest {
string request_id = 1;
string app_code = 2;
repeated int64 resource_ids = 3;
int64 operator_user_id = 4;
}
message BatchDeleteResourcesResponse {
repeated Resource resources = 1;
}
message ResourceResponse { message ResourceResponse {
Resource resource = 1; Resource resource = 1;
} }
@ -807,6 +825,17 @@ message CreateGiftConfigRequest {
string cp_relation_type = 21; string cp_relation_type = 21;
} }
message BatchCreateGiftConfigsRequest {
string request_id = 1;
string app_code = 2;
repeated CreateGiftConfigRequest items = 3;
int64 operator_user_id = 4;
}
message BatchCreateGiftConfigsResponse {
repeated GiftConfig gifts = 1;
}
message UpdateGiftConfigRequest { message UpdateGiftConfigRequest {
string request_id = 1; string request_id = 1;
string app_code = 2; string app_code = 2;
@ -2262,6 +2291,8 @@ service WalletService {
rpc CreateResource(CreateResourceRequest) returns (ResourceResponse); rpc CreateResource(CreateResourceRequest) returns (ResourceResponse);
rpc UpdateResource(UpdateResourceRequest) returns (ResourceResponse); rpc UpdateResource(UpdateResourceRequest) returns (ResourceResponse);
rpc SetResourceStatus(SetResourceStatusRequest) returns (ResourceResponse); rpc SetResourceStatus(SetResourceStatusRequest) returns (ResourceResponse);
rpc DeleteResource(DeleteResourceRequest) returns (ResourceResponse);
rpc BatchDeleteResources(BatchDeleteResourcesRequest) returns (BatchDeleteResourcesResponse);
rpc ListResourceGroups(ListResourceGroupsRequest) returns (ListResourceGroupsResponse); rpc ListResourceGroups(ListResourceGroupsRequest) returns (ListResourceGroupsResponse);
rpc GetResourceGroup(GetResourceGroupRequest) returns (GetResourceGroupResponse); rpc GetResourceGroup(GetResourceGroupRequest) returns (GetResourceGroupResponse);
rpc CreateResourceGroup(CreateResourceGroupRequest) returns (ResourceGroupResponse); rpc CreateResourceGroup(CreateResourceGroupRequest) returns (ResourceGroupResponse);
@ -2271,6 +2302,7 @@ service WalletService {
rpc ListGiftTypeConfigs(ListGiftTypeConfigsRequest) returns (ListGiftTypeConfigsResponse); rpc ListGiftTypeConfigs(ListGiftTypeConfigsRequest) returns (ListGiftTypeConfigsResponse);
rpc GetGiftCatalogVersion(GetGiftCatalogVersionRequest) returns (GetGiftCatalogVersionResponse); rpc GetGiftCatalogVersion(GetGiftCatalogVersionRequest) returns (GetGiftCatalogVersionResponse);
rpc CreateGiftConfig(CreateGiftConfigRequest) returns (GiftConfigResponse); rpc CreateGiftConfig(CreateGiftConfigRequest) returns (GiftConfigResponse);
rpc BatchCreateGiftConfigs(BatchCreateGiftConfigsRequest) returns (BatchCreateGiftConfigsResponse);
rpc UpdateGiftConfig(UpdateGiftConfigRequest) returns (GiftConfigResponse); rpc UpdateGiftConfig(UpdateGiftConfigRequest) returns (GiftConfigResponse);
rpc SetGiftConfigStatus(SetGiftConfigStatusRequest) returns (GiftConfigResponse); rpc SetGiftConfigStatus(SetGiftConfigStatusRequest) returns (GiftConfigResponse);
rpc DeleteGiftConfig(DeleteGiftConfigRequest) returns (GiftConfigResponse); rpc DeleteGiftConfig(DeleteGiftConfigRequest) returns (GiftConfigResponse);

View File

@ -222,6 +222,8 @@ const (
WalletService_CreateResource_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateResource" WalletService_CreateResource_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateResource"
WalletService_UpdateResource_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateResource" WalletService_UpdateResource_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateResource"
WalletService_SetResourceStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetResourceStatus" WalletService_SetResourceStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetResourceStatus"
WalletService_DeleteResource_FullMethodName = "/hyapp.wallet.v1.WalletService/DeleteResource"
WalletService_BatchDeleteResources_FullMethodName = "/hyapp.wallet.v1.WalletService/BatchDeleteResources"
WalletService_ListResourceGroups_FullMethodName = "/hyapp.wallet.v1.WalletService/ListResourceGroups" WalletService_ListResourceGroups_FullMethodName = "/hyapp.wallet.v1.WalletService/ListResourceGroups"
WalletService_GetResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/GetResourceGroup" WalletService_GetResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/GetResourceGroup"
WalletService_CreateResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateResourceGroup" WalletService_CreateResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateResourceGroup"
@ -231,6 +233,7 @@ const (
WalletService_ListGiftTypeConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/ListGiftTypeConfigs" WalletService_ListGiftTypeConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/ListGiftTypeConfigs"
WalletService_GetGiftCatalogVersion_FullMethodName = "/hyapp.wallet.v1.WalletService/GetGiftCatalogVersion" WalletService_GetGiftCatalogVersion_FullMethodName = "/hyapp.wallet.v1.WalletService/GetGiftCatalogVersion"
WalletService_CreateGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateGiftConfig" WalletService_CreateGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateGiftConfig"
WalletService_BatchCreateGiftConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/BatchCreateGiftConfigs"
WalletService_UpdateGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateGiftConfig" WalletService_UpdateGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateGiftConfig"
WalletService_SetGiftConfigStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetGiftConfigStatus" WalletService_SetGiftConfigStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetGiftConfigStatus"
WalletService_DeleteGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/DeleteGiftConfig" WalletService_DeleteGiftConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/DeleteGiftConfig"
@ -328,6 +331,8 @@ type WalletServiceClient interface {
CreateResource(ctx context.Context, in *CreateResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error) CreateResource(ctx context.Context, in *CreateResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error) UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
SetResourceStatus(ctx context.Context, in *SetResourceStatusRequest, opts ...grpc.CallOption) (*ResourceResponse, error) SetResourceStatus(ctx context.Context, in *SetResourceStatusRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
DeleteResource(ctx context.Context, in *DeleteResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
BatchDeleteResources(ctx context.Context, in *BatchDeleteResourcesRequest, opts ...grpc.CallOption) (*BatchDeleteResourcesResponse, error)
ListResourceGroups(ctx context.Context, in *ListResourceGroupsRequest, opts ...grpc.CallOption) (*ListResourceGroupsResponse, error) ListResourceGroups(ctx context.Context, in *ListResourceGroupsRequest, opts ...grpc.CallOption) (*ListResourceGroupsResponse, error)
GetResourceGroup(ctx context.Context, in *GetResourceGroupRequest, opts ...grpc.CallOption) (*GetResourceGroupResponse, error) GetResourceGroup(ctx context.Context, in *GetResourceGroupRequest, opts ...grpc.CallOption) (*GetResourceGroupResponse, error)
CreateResourceGroup(ctx context.Context, in *CreateResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error) CreateResourceGroup(ctx context.Context, in *CreateResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error)
@ -337,6 +342,7 @@ type WalletServiceClient interface {
ListGiftTypeConfigs(ctx context.Context, in *ListGiftTypeConfigsRequest, opts ...grpc.CallOption) (*ListGiftTypeConfigsResponse, error) ListGiftTypeConfigs(ctx context.Context, in *ListGiftTypeConfigsRequest, opts ...grpc.CallOption) (*ListGiftTypeConfigsResponse, error)
GetGiftCatalogVersion(ctx context.Context, in *GetGiftCatalogVersionRequest, opts ...grpc.CallOption) (*GetGiftCatalogVersionResponse, error) GetGiftCatalogVersion(ctx context.Context, in *GetGiftCatalogVersionRequest, opts ...grpc.CallOption) (*GetGiftCatalogVersionResponse, error)
CreateGiftConfig(ctx context.Context, in *CreateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) CreateGiftConfig(ctx context.Context, in *CreateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error)
BatchCreateGiftConfigs(ctx context.Context, in *BatchCreateGiftConfigsRequest, opts ...grpc.CallOption) (*BatchCreateGiftConfigsResponse, error)
UpdateGiftConfig(ctx context.Context, in *UpdateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) UpdateGiftConfig(ctx context.Context, in *UpdateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error)
SetGiftConfigStatus(ctx context.Context, in *SetGiftConfigStatusRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) SetGiftConfigStatus(ctx context.Context, in *SetGiftConfigStatusRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error)
DeleteGiftConfig(ctx context.Context, in *DeleteGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) DeleteGiftConfig(ctx context.Context, in *DeleteGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error)
@ -625,6 +631,26 @@ func (c *walletServiceClient) SetResourceStatus(ctx context.Context, in *SetReso
return out, nil return out, nil
} }
func (c *walletServiceClient) DeleteResource(ctx context.Context, in *DeleteResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceResponse)
err := c.cc.Invoke(ctx, WalletService_DeleteResource_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) BatchDeleteResources(ctx context.Context, in *BatchDeleteResourcesRequest, opts ...grpc.CallOption) (*BatchDeleteResourcesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(BatchDeleteResourcesResponse)
err := c.cc.Invoke(ctx, WalletService_BatchDeleteResources_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) ListResourceGroups(ctx context.Context, in *ListResourceGroupsRequest, opts ...grpc.CallOption) (*ListResourceGroupsResponse, error) { func (c *walletServiceClient) ListResourceGroups(ctx context.Context, in *ListResourceGroupsRequest, opts ...grpc.CallOption) (*ListResourceGroupsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListResourceGroupsResponse) out := new(ListResourceGroupsResponse)
@ -715,6 +741,16 @@ func (c *walletServiceClient) CreateGiftConfig(ctx context.Context, in *CreateGi
return out, nil return out, nil
} }
func (c *walletServiceClient) BatchCreateGiftConfigs(ctx context.Context, in *BatchCreateGiftConfigsRequest, opts ...grpc.CallOption) (*BatchCreateGiftConfigsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(BatchCreateGiftConfigsResponse)
err := c.cc.Invoke(ctx, WalletService_BatchCreateGiftConfigs_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) UpdateGiftConfig(ctx context.Context, in *UpdateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) { func (c *walletServiceClient) UpdateGiftConfig(ctx context.Context, in *UpdateGiftConfigRequest, opts ...grpc.CallOption) (*GiftConfigResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GiftConfigResponse) out := new(GiftConfigResponse)
@ -1422,6 +1458,8 @@ type WalletServiceServer interface {
CreateResource(context.Context, *CreateResourceRequest) (*ResourceResponse, error) CreateResource(context.Context, *CreateResourceRequest) (*ResourceResponse, error)
UpdateResource(context.Context, *UpdateResourceRequest) (*ResourceResponse, error) UpdateResource(context.Context, *UpdateResourceRequest) (*ResourceResponse, error)
SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error)
DeleteResource(context.Context, *DeleteResourceRequest) (*ResourceResponse, error)
BatchDeleteResources(context.Context, *BatchDeleteResourcesRequest) (*BatchDeleteResourcesResponse, error)
ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error)
GetResourceGroup(context.Context, *GetResourceGroupRequest) (*GetResourceGroupResponse, error) GetResourceGroup(context.Context, *GetResourceGroupRequest) (*GetResourceGroupResponse, error)
CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error) CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error)
@ -1431,6 +1469,7 @@ type WalletServiceServer interface {
ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error) ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error)
GetGiftCatalogVersion(context.Context, *GetGiftCatalogVersionRequest) (*GetGiftCatalogVersionResponse, error) GetGiftCatalogVersion(context.Context, *GetGiftCatalogVersionRequest) (*GetGiftCatalogVersionResponse, error)
CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error)
BatchCreateGiftConfigs(context.Context, *BatchCreateGiftConfigsRequest) (*BatchCreateGiftConfigsResponse, error)
UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error)
SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error)
DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error) DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error)
@ -1572,6 +1611,12 @@ func (UnimplementedWalletServiceServer) UpdateResource(context.Context, *UpdateR
func (UnimplementedWalletServiceServer) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) { func (UnimplementedWalletServiceServer) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceStatus not implemented") return nil, status.Errorf(codes.Unimplemented, "method SetResourceStatus not implemented")
} }
func (UnimplementedWalletServiceServer) DeleteResource(context.Context, *DeleteResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteResource not implemented")
}
func (UnimplementedWalletServiceServer) BatchDeleteResources(context.Context, *BatchDeleteResourcesRequest) (*BatchDeleteResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchDeleteResources not implemented")
}
func (UnimplementedWalletServiceServer) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) { func (UnimplementedWalletServiceServer) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGroups not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListResourceGroups not implemented")
} }
@ -1599,6 +1644,9 @@ func (UnimplementedWalletServiceServer) GetGiftCatalogVersion(context.Context, *
func (UnimplementedWalletServiceServer) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) { func (UnimplementedWalletServiceServer) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateGiftConfig not implemented") return nil, status.Errorf(codes.Unimplemented, "method CreateGiftConfig not implemented")
} }
func (UnimplementedWalletServiceServer) BatchCreateGiftConfigs(context.Context, *BatchCreateGiftConfigsRequest) (*BatchCreateGiftConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchCreateGiftConfigs not implemented")
}
func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) { func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateGiftConfig not implemented") return nil, status.Errorf(codes.Unimplemented, "method UpdateGiftConfig not implemented")
} }
@ -2202,6 +2250,42 @@ func _WalletService_SetResourceStatus_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletService_DeleteResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteResourceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).DeleteResource(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_DeleteResource_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).DeleteResource(ctx, req.(*DeleteResourceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_BatchDeleteResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BatchDeleteResourcesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).BatchDeleteResources(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_BatchDeleteResources_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).BatchDeleteResources(ctx, req.(*BatchDeleteResourcesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_ListResourceGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletService_ListResourceGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListResourceGroupsRequest) in := new(ListResourceGroupsRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -2364,6 +2448,24 @@ func _WalletService_CreateGiftConfig_Handler(srv interface{}, ctx context.Contex
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletService_BatchCreateGiftConfigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BatchCreateGiftConfigsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).BatchCreateGiftConfigs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_BatchCreateGiftConfigs_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).BatchCreateGiftConfigs(ctx, req.(*BatchCreateGiftConfigsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_UpdateGiftConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletService_UpdateGiftConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateGiftConfigRequest) in := new(UpdateGiftConfigRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -3679,6 +3781,14 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "SetResourceStatus", MethodName: "SetResourceStatus",
Handler: _WalletService_SetResourceStatus_Handler, Handler: _WalletService_SetResourceStatus_Handler,
}, },
{
MethodName: "DeleteResource",
Handler: _WalletService_DeleteResource_Handler,
},
{
MethodName: "BatchDeleteResources",
Handler: _WalletService_BatchDeleteResources_Handler,
},
{ {
MethodName: "ListResourceGroups", MethodName: "ListResourceGroups",
Handler: _WalletService_ListResourceGroups_Handler, Handler: _WalletService_ListResourceGroups_Handler,
@ -3715,6 +3825,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "CreateGiftConfig", MethodName: "CreateGiftConfig",
Handler: _WalletService_CreateGiftConfig_Handler, Handler: _WalletService_CreateGiftConfig_Handler,
}, },
{
MethodName: "BatchCreateGiftConfigs",
Handler: _WalletService_BatchCreateGiftConfigs_Handler,
},
{ {
MethodName: "UpdateGiftConfig", MethodName: "UpdateGiftConfig",
Handler: _WalletService_UpdateGiftConfig_Handler, Handler: _WalletService_UpdateGiftConfig_Handler,

View File

@ -15,6 +15,8 @@ type Client interface {
CreateResource(ctx context.Context, req *walletv1.CreateResourceRequest) (*walletv1.ResourceResponse, error) CreateResource(ctx context.Context, req *walletv1.CreateResourceRequest) (*walletv1.ResourceResponse, error)
UpdateResource(ctx context.Context, req *walletv1.UpdateResourceRequest) (*walletv1.ResourceResponse, error) UpdateResource(ctx context.Context, req *walletv1.UpdateResourceRequest) (*walletv1.ResourceResponse, error)
SetResourceStatus(ctx context.Context, req *walletv1.SetResourceStatusRequest) (*walletv1.ResourceResponse, error) SetResourceStatus(ctx context.Context, req *walletv1.SetResourceStatusRequest) (*walletv1.ResourceResponse, error)
DeleteResource(ctx context.Context, req *walletv1.DeleteResourceRequest) (*walletv1.ResourceResponse, error)
BatchDeleteResources(ctx context.Context, req *walletv1.BatchDeleteResourcesRequest) (*walletv1.BatchDeleteResourcesResponse, error)
ListResourceGroups(ctx context.Context, req *walletv1.ListResourceGroupsRequest) (*walletv1.ListResourceGroupsResponse, error) ListResourceGroups(ctx context.Context, req *walletv1.ListResourceGroupsRequest) (*walletv1.ListResourceGroupsResponse, error)
GetResourceGroup(ctx context.Context, req *walletv1.GetResourceGroupRequest) (*walletv1.GetResourceGroupResponse, error) GetResourceGroup(ctx context.Context, req *walletv1.GetResourceGroupRequest) (*walletv1.GetResourceGroupResponse, error)
CreateResourceGroup(ctx context.Context, req *walletv1.CreateResourceGroupRequest) (*walletv1.ResourceGroupResponse, error) CreateResourceGroup(ctx context.Context, req *walletv1.CreateResourceGroupRequest) (*walletv1.ResourceGroupResponse, error)
@ -23,6 +25,7 @@ type Client interface {
ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error)
ListGiftTypeConfigs(ctx context.Context, req *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error) ListGiftTypeConfigs(ctx context.Context, req *walletv1.ListGiftTypeConfigsRequest) (*walletv1.ListGiftTypeConfigsResponse, error)
CreateGiftConfig(ctx context.Context, req *walletv1.CreateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) CreateGiftConfig(ctx context.Context, req *walletv1.CreateGiftConfigRequest) (*walletv1.GiftConfigResponse, error)
BatchCreateGiftConfigs(ctx context.Context, req *walletv1.BatchCreateGiftConfigsRequest) (*walletv1.BatchCreateGiftConfigsResponse, error)
UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error)
SetGiftConfigStatus(ctx context.Context, req *walletv1.SetGiftConfigStatusRequest) (*walletv1.GiftConfigResponse, error) SetGiftConfigStatus(ctx context.Context, req *walletv1.SetGiftConfigStatusRequest) (*walletv1.GiftConfigResponse, error)
DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error)
@ -93,6 +96,14 @@ func (c *GRPCClient) SetResourceStatus(ctx context.Context, req *walletv1.SetRes
return c.client.SetResourceStatus(ctx, req) return c.client.SetResourceStatus(ctx, req)
} }
func (c *GRPCClient) DeleteResource(ctx context.Context, req *walletv1.DeleteResourceRequest) (*walletv1.ResourceResponse, error) {
return c.client.DeleteResource(ctx, req)
}
func (c *GRPCClient) BatchDeleteResources(ctx context.Context, req *walletv1.BatchDeleteResourcesRequest) (*walletv1.BatchDeleteResourcesResponse, error) {
return c.client.BatchDeleteResources(ctx, req)
}
func (c *GRPCClient) ListResourceGroups(ctx context.Context, req *walletv1.ListResourceGroupsRequest) (*walletv1.ListResourceGroupsResponse, error) { func (c *GRPCClient) ListResourceGroups(ctx context.Context, req *walletv1.ListResourceGroupsRequest) (*walletv1.ListResourceGroupsResponse, error) {
return c.client.ListResourceGroups(ctx, req) return c.client.ListResourceGroups(ctx, req)
} }
@ -125,6 +136,10 @@ func (c *GRPCClient) CreateGiftConfig(ctx context.Context, req *walletv1.CreateG
return c.client.CreateGiftConfig(ctx, req) return c.client.CreateGiftConfig(ctx, req)
} }
func (c *GRPCClient) BatchCreateGiftConfigs(ctx context.Context, req *walletv1.BatchCreateGiftConfigsRequest) (*walletv1.BatchCreateGiftConfigsResponse, error) {
return c.client.BatchCreateGiftConfigs(ctx, req)
}
func (c *GRPCClient) UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) { func (c *GRPCClient) UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) {
return c.client.UpdateGiftConfig(ctx, req) return c.client.UpdateGiftConfig(ctx, req)
} }

View File

@ -231,6 +231,53 @@ func (h *Handler) UpdateResource(c *gin.Context) {
response.OK(c, resource) response.OK(c, resource)
} }
func (h *Handler) DeleteResource(c *gin.Context) {
resourceID, ok := parseID(c, "resource_id")
if !ok {
return
}
resp, err := h.wallet.DeleteResource(c.Request.Context(), &walletv1.DeleteResourceRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appctx.FromContext(c.Request.Context()),
ResourceId: resourceID,
OperatorUserId: actorID(c),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
resource := resourceFromProto(resp.GetResource())
h.auditLog(c, "delete-resource", "resources", fmt.Sprintf("%d", resource.ResourceID), "success", resource.ResourceCode)
response.OK(c, resource)
}
func (h *Handler) BatchDeleteResources(c *gin.Context) {
var req resourceDeleteBatchRequest
if err := c.ShouldBindJSON(&req); err != nil || len(req.ResourceIDs) == 0 {
response.BadRequest(c, "资源参数不正确")
return
}
resp, err := h.wallet.BatchDeleteResources(c.Request.Context(), &walletv1.BatchDeleteResourcesRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appctx.FromContext(c.Request.Context()),
ResourceIds: req.ResourceIDs,
OperatorUserId: actorID(c),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
items := make([]resourceDTO, 0, len(resp.GetResources()))
deletedIDs := make([]string, 0, len(resp.GetResources()))
for _, item := range resp.GetResources() {
resource := resourceFromProto(item)
items = append(items, resource)
deletedIDs = append(deletedIDs, fmt.Sprintf("%d", resource.ResourceID))
}
h.auditLog(c, "delete-resources", "resources", "batch", "success", strings.Join(deletedIDs, ","))
response.OK(c, items)
}
func (h *Handler) UpdateMP4ResourceLayouts(c *gin.Context) { func (h *Handler) UpdateMP4ResourceLayouts(c *gin.Context) {
var req resourceMP4LayoutBatchRequest var req resourceMP4LayoutBatchRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
@ -500,6 +547,37 @@ func (h *Handler) CreateGift(c *gin.Context) {
response.Created(c, gift) response.Created(c, gift)
} }
func (h *Handler) BatchCreateGifts(c *gin.Context) {
var req giftBatchRequest
if err := c.ShouldBindJSON(&req); err != nil || len(req.Items) == 0 {
response.BadRequest(c, "礼物参数不正确")
return
}
items := make([]*walletv1.CreateGiftConfigRequest, 0, len(req.Items))
for _, item := range req.Items {
items = append(items, item.createProto(c))
}
resp, err := h.wallet.BatchCreateGiftConfigs(c.Request.Context(), &walletv1.BatchCreateGiftConfigsRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appctx.FromContext(c.Request.Context()),
Items: items,
OperatorUserId: actorID(c),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
gifts := make([]giftDTO, 0, len(resp.GetGifts()))
giftIDs := make([]string, 0, len(resp.GetGifts()))
for _, item := range resp.GetGifts() {
gift := giftFromProto(item)
gifts = append(gifts, gift)
giftIDs = append(giftIDs, gift.GiftID)
}
h.auditLog(c, "create-gifts", "gift_configs", "batch", "success", strings.Join(giftIDs, ","))
response.Created(c, gifts)
}
func (h *Handler) UpdateGift(c *gin.Context) { func (h *Handler) UpdateGift(c *gin.Context) {
giftID := strings.TrimSpace(c.Param("gift_id")) giftID := strings.TrimSpace(c.Param("gift_id"))
if giftID == "" { if giftID == "" {

View File

@ -136,6 +136,74 @@ func TestDeleteGiftOnlyCallsGiftConfigDelete(t *testing.T) {
} }
} }
func TestDeleteResourceCallsWalletDeleteResource(t *testing.T) {
wallet := &mockResourceWallet{resources: map[int64]*walletv1.Resource{
11: {AppCode: "lalu", ResourceId: 11, ResourceCode: "rose_resource", ResourceType: "gift", Name: "Rose"},
}}
router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil))
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodDelete, "/admin/resources/11", nil)
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("delete resource status mismatch: %d %s", recorder.Code, recorder.Body.String())
}
if len(wallet.deletedResources) != 1 {
t.Fatalf("expected one resource delete request, got %d", len(wallet.deletedResources))
}
deleteReq := wallet.deletedResources[0]
if deleteReq.GetAppCode() != "lalu" || deleteReq.GetResourceId() != 11 || deleteReq.GetOperatorUserId() != 7 {
t.Fatalf("delete resource request mismatch: %+v", deleteReq)
}
}
func TestBatchDeleteResourcesCallsWalletBatchDelete(t *testing.T) {
wallet := &mockResourceWallet{resources: map[int64]*walletv1.Resource{
11: {AppCode: "lalu", ResourceId: 11, ResourceCode: "rose_resource", ResourceType: "gift", Name: "Rose"},
12: {AppCode: "lalu", ResourceId: 12, ResourceCode: "ring_resource", ResourceType: "gift", Name: "Ring"},
}}
router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil))
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/admin/resources/batch-delete", strings.NewReader(`{"resourceIds":[11,12]}`))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("batch delete resources status mismatch: %d %s", recorder.Code, recorder.Body.String())
}
if len(wallet.batchDeletedResources) != 1 {
t.Fatalf("expected one batch resource delete request, got %d", len(wallet.batchDeletedResources))
}
deleteReq := wallet.batchDeletedResources[0]
if deleteReq.GetAppCode() != "lalu" || fmt.Sprint(deleteReq.GetResourceIds()) != "[11 12]" || deleteReq.GetOperatorUserId() != 7 {
t.Fatalf("batch delete resource request mismatch: %+v", deleteReq)
}
}
func TestBatchCreateGiftsCallsWalletBatchCreate(t *testing.T) {
wallet := &mockResourceWallet{}
router := newResourceHandlerTestRouter(New(wallet, nil, nil, time.Second, nil))
body := `{"items":[{"giftId":"rose","resourceId":11,"status":"active","name":"Rose","priceVersion":"default","giftTypeCode":"normal","chargeAssetType":"COIN","coinPrice":10,"regionIds":[0]},{"giftId":"ring","resourceId":12,"status":"active","name":"Ring","priceVersion":"default","giftTypeCode":"normal","chargeAssetType":"COIN","coinPrice":20,"regionIds":[0]}]}`
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/admin/gifts/batch", strings.NewReader(body))
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusCreated {
t.Fatalf("batch create gifts status mismatch: %d %s", recorder.Code, recorder.Body.String())
}
if len(wallet.batchCreatedGifts) != 1 {
t.Fatalf("expected one batch gift create request, got %d", len(wallet.batchCreatedGifts))
}
createReq := wallet.batchCreatedGifts[0]
if createReq.GetAppCode() != "lalu" || len(createReq.GetItems()) != 2 || createReq.GetItems()[0].GetGiftId() != "rose" || createReq.GetItems()[1].GetGiftId() != "ring" {
t.Fatalf("batch create gift request mismatch: %+v", createReq)
}
}
func TestIdentityAutoGrantConfigRouteDoesNotHitResourceGroupID(t *testing.T) { func TestIdentityAutoGrantConfigRouteDoesNotHitResourceGroupID(t *testing.T) {
router := newResourceHandlerTestRouter(New(&mockResourceWallet{}, nil, nil, time.Second, nil)) router := newResourceHandlerTestRouter(New(&mockResourceWallet{}, nil, nil, time.Second, nil))
@ -215,6 +283,7 @@ func newResourceHandlerTestRouter(handler *Handler) *gin.Engine {
"gift:create", "gift:create",
"gift:status", "gift:status",
"resource:create", "resource:create",
"resource:delete",
"resource-grant:create", "resource-grant:create",
"resource-grant:revoke", "resource-grant:revoke",
"resource-grant:view", "resource-grant:view",
@ -234,10 +303,13 @@ func newResourceHandlerTestRouter(handler *Handler) *gin.Engine {
type mockResourceWallet struct { type mockResourceWallet struct {
walletclient.Client walletclient.Client
resources map[int64]*walletv1.Resource resources map[int64]*walletv1.Resource
updates []*walletv1.UpdateResourceRequest updates []*walletv1.UpdateResourceRequest
deletedGifts []*walletv1.DeleteGiftConfigRequest deletedResources []*walletv1.DeleteResourceRequest
listGrantRequests []*walletv1.ListResourceGrantsRequest batchDeletedResources []*walletv1.BatchDeleteResourcesRequest
batchCreatedGifts []*walletv1.BatchCreateGiftConfigsRequest
deletedGifts []*walletv1.DeleteGiftConfigRequest
listGrantRequests []*walletv1.ListResourceGrantsRequest
} }
func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) { func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) {
@ -273,6 +345,43 @@ func (m *mockResourceWallet) UpdateResource(ctx context.Context, req *walletv1.U
}}, nil }}, nil
} }
func (m *mockResourceWallet) DeleteResource(ctx context.Context, req *walletv1.DeleteResourceRequest) (*walletv1.ResourceResponse, error) {
m.deletedResources = append(m.deletedResources, req)
resource := m.resources[req.GetResourceId()]
if resource == nil {
return nil, fmt.Errorf("resource not found")
}
return &walletv1.ResourceResponse{Resource: resource}, nil
}
func (m *mockResourceWallet) BatchDeleteResources(ctx context.Context, req *walletv1.BatchDeleteResourcesRequest) (*walletv1.BatchDeleteResourcesResponse, error) {
m.batchDeletedResources = append(m.batchDeletedResources, req)
resp := &walletv1.BatchDeleteResourcesResponse{Resources: make([]*walletv1.Resource, 0, len(req.GetResourceIds()))}
for _, resourceID := range req.GetResourceIds() {
resource := m.resources[resourceID]
if resource == nil {
return nil, fmt.Errorf("resource not found")
}
resp.Resources = append(resp.Resources, resource)
}
return resp, nil
}
func (m *mockResourceWallet) BatchCreateGiftConfigs(ctx context.Context, req *walletv1.BatchCreateGiftConfigsRequest) (*walletv1.BatchCreateGiftConfigsResponse, error) {
m.batchCreatedGifts = append(m.batchCreatedGifts, req)
resp := &walletv1.BatchCreateGiftConfigsResponse{Gifts: make([]*walletv1.GiftConfig, 0, len(req.GetItems()))}
for _, item := range req.GetItems() {
resp.Gifts = append(resp.Gifts, &walletv1.GiftConfig{
AppCode: item.GetAppCode(),
GiftId: item.GetGiftId(),
ResourceId: item.GetResourceId(),
Status: item.GetStatus(),
Name: item.GetName(),
})
}
return resp, nil
}
func (m *mockResourceWallet) DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) { func (m *mockResourceWallet) DeleteGiftConfig(ctx context.Context, req *walletv1.DeleteGiftConfigRequest) (*walletv1.GiftConfigResponse, error) {
m.deletedGifts = append(m.deletedGifts, req) m.deletedGifts = append(m.deletedGifts, req)
return &walletv1.GiftConfigResponse{Gift: &walletv1.GiftConfig{ return &walletv1.GiftConfigResponse{Gift: &walletv1.GiftConfig{

View File

@ -89,6 +89,10 @@ type resourceMP4LayoutBatchRequest struct {
Items []resourceMP4LayoutUpdateRequest `json:"items"` Items []resourceMP4LayoutUpdateRequest `json:"items"`
} }
type resourceDeleteBatchRequest struct {
ResourceIDs []int64 `json:"resourceIds"`
}
type resourceMP4LayoutUpdateRequest struct { type resourceMP4LayoutUpdateRequest struct {
ResourceID int64 `json:"resourceId"` ResourceID int64 `json:"resourceId"`
MetadataJSON string `json:"metadataJson"` MetadataJSON string `json:"metadataJson"`
@ -191,6 +195,10 @@ type giftRequest struct {
RegionIDs []int64 `json:"regionIds"` RegionIDs []int64 `json:"regionIds"`
} }
type giftBatchRequest struct {
Items []giftRequest `json:"items"`
}
type giftTypeRequest struct { type giftTypeRequest struct {
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
TabName string `json:"tabName"` TabName string `json:"tabName"`

View File

@ -14,8 +14,10 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
protected.GET("/admin/resources", middleware.RequirePermission("resource:view"), h.ListResources) protected.GET("/admin/resources", middleware.RequirePermission("resource:view"), h.ListResources)
protected.POST("/admin/resources", middleware.RequirePermission("resource:create"), h.CreateResource) protected.POST("/admin/resources", middleware.RequirePermission("resource:create"), h.CreateResource)
protected.PUT("/admin/resources/mp4-layouts/batch", middleware.RequirePermission("resource:update"), h.UpdateMP4ResourceLayouts) protected.PUT("/admin/resources/mp4-layouts/batch", middleware.RequirePermission("resource:update"), h.UpdateMP4ResourceLayouts)
protected.POST("/admin/resources/batch-delete", middleware.RequirePermission("resource:delete"), h.BatchDeleteResources)
protected.GET("/admin/resources/:resource_id", middleware.RequirePermission("resource:view"), h.GetResource) protected.GET("/admin/resources/:resource_id", middleware.RequirePermission("resource:view"), h.GetResource)
protected.PUT("/admin/resources/:resource_id", middleware.RequirePermission("resource:update"), h.UpdateResource) protected.PUT("/admin/resources/:resource_id", middleware.RequirePermission("resource:update"), h.UpdateResource)
protected.DELETE("/admin/resources/:resource_id", middleware.RequirePermission("resource:delete"), h.DeleteResource)
protected.POST("/admin/resources/:resource_id/enable", middleware.RequirePermission("resource:update"), h.EnableResource) protected.POST("/admin/resources/:resource_id/enable", middleware.RequirePermission("resource:update"), h.EnableResource)
protected.POST("/admin/resources/:resource_id/disable", middleware.RequirePermission("resource:update"), h.DisableResource) protected.POST("/admin/resources/:resource_id/disable", middleware.RequirePermission("resource:update"), h.DisableResource)
@ -38,6 +40,7 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
protected.PUT("/admin/gift-types", middleware.RequirePermission("gift:update"), h.UpdateGiftTypes) protected.PUT("/admin/gift-types", middleware.RequirePermission("gift:update"), h.UpdateGiftTypes)
protected.PUT("/admin/gift-types/:type_code", middleware.RequirePermission("gift:update"), h.UpdateGiftType) protected.PUT("/admin/gift-types/:type_code", middleware.RequirePermission("gift:update"), h.UpdateGiftType)
protected.POST("/admin/gifts", middleware.RequirePermission("gift:create"), h.CreateGift) protected.POST("/admin/gifts", middleware.RequirePermission("gift:create"), h.CreateGift)
protected.POST("/admin/gifts/batch", middleware.RequirePermission("gift:create"), h.BatchCreateGifts)
protected.PUT("/admin/gifts/:gift_id", middleware.RequirePermission("gift:update"), h.UpdateGift) protected.PUT("/admin/gifts/:gift_id", middleware.RequirePermission("gift:update"), h.UpdateGift)
protected.DELETE("/admin/gifts/:gift_id", middleware.RequirePermission("gift:delete"), h.DeleteGift) protected.DELETE("/admin/gifts/:gift_id", middleware.RequirePermission("gift:delete"), h.DeleteGift)
protected.POST("/admin/gifts/:gift_id/enable", middleware.RequirePermission("gift:status"), h.EnableGift) protected.POST("/admin/gifts/:gift_id/enable", middleware.RequirePermission("gift:status"), h.EnableGift)

View File

@ -15,6 +15,7 @@ const (
StatusActive = "active" StatusActive = "active"
StatusDisabled = "disabled" StatusDisabled = "disabled"
StatusDeleted = "deleted"
GrantStrategyWalletCredit = "wallet_credit" GrantStrategyWalletCredit = "wallet_credit"
GrantStrategyNewEntitlement = "new_entitlement" GrantStrategyNewEntitlement = "new_entitlement"

View File

@ -77,6 +77,12 @@ type StatusCommand struct {
OperatorUserID int64 OperatorUserID int64
} }
type BatchDeleteResourcesCommand struct {
AppCode string
ResourceIDs []int64
OperatorUserID int64
}
func NormalizeStatus(value string) string { func NormalizeStatus(value string) string {
value = strings.ToLower(strings.TrimSpace(value)) value = strings.ToLower(strings.TrimSpace(value))
if value == "" { if value == "" {

View File

@ -158,6 +158,8 @@ type ResourceCatalogStore interface {
CreateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error) CreateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error)
UpdateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error) UpdateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error)
SetResourceStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error) SetResourceStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error)
DeleteResource(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error)
BatchDeleteResources(ctx context.Context, command resourcedomain.BatchDeleteResourcesCommand) ([]resourcedomain.Resource, error)
ListResourceGroups(ctx context.Context, query resourcedomain.ListResourceGroupsQuery) ([]resourcedomain.ResourceGroup, int64, error) ListResourceGroups(ctx context.Context, query resourcedomain.ListResourceGroupsQuery) ([]resourcedomain.ResourceGroup, int64, error)
GetResourceGroup(ctx context.Context, groupID int64) (resourcedomain.ResourceGroup, error) GetResourceGroup(ctx context.Context, groupID int64) (resourcedomain.ResourceGroup, error)
CreateResourceGroup(ctx context.Context, command resourcedomain.ResourceGroupCommand) (resourcedomain.ResourceGroup, error) CreateResourceGroup(ctx context.Context, command resourcedomain.ResourceGroupCommand) (resourcedomain.ResourceGroup, error)
@ -167,6 +169,7 @@ type ResourceCatalogStore interface {
ListGiftTypeConfigs(ctx context.Context, query resourcedomain.ListGiftTypeConfigsQuery) ([]resourcedomain.GiftTypeConfig, error) ListGiftTypeConfigs(ctx context.Context, query resourcedomain.ListGiftTypeConfigsQuery) ([]resourcedomain.GiftTypeConfig, error)
GetGiftCatalogVersion(ctx context.Context, appCode string) (int64, error) GetGiftCatalogVersion(ctx context.Context, appCode string) (int64, error)
CreateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) CreateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error)
BatchCreateGiftConfigs(ctx context.Context, commands []resourcedomain.GiftConfigCommand) ([]resourcedomain.GiftConfig, error)
UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error)
SetGiftConfigStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) SetGiftConfigStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error)
DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error)

View File

@ -59,6 +59,24 @@ func (s *Service) SetResourceStatus(ctx context.Context, command resourcedomain.
return s.repository.SetResourceStatus(ctx, command) return s.repository.SetResourceStatus(ctx, command)
} }
func (s *Service) DeleteResource(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error) {
if s.repository == nil {
return resourcedomain.Resource{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.DeleteResource(ctx, command)
}
func (s *Service) BatchDeleteResources(ctx context.Context, command resourcedomain.BatchDeleteResourcesCommand) ([]resourcedomain.Resource, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.BatchDeleteResources(ctx, command)
}
func (s *Service) ListResourceGroups(ctx context.Context, query resourcedomain.ListResourceGroupsQuery) ([]resourcedomain.ResourceGroup, int64, error) { func (s *Service) ListResourceGroups(ctx context.Context, query resourcedomain.ListResourceGroupsQuery) ([]resourcedomain.ResourceGroup, int64, error) {
if s.repository == nil { if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured") return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
@ -145,6 +163,26 @@ func (s *Service) CreateGiftConfig(ctx context.Context, command resourcedomain.G
return s.repository.CreateGiftConfig(ctx, command) return s.repository.CreateGiftConfig(ctx, command)
} }
func (s *Service) BatchCreateGiftConfigs(ctx context.Context, appCode string, commands []resourcedomain.GiftConfigCommand, operatorUserID int64) ([]resourcedomain.GiftConfig, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
if len(commands) == 0 {
return nil, xerr.New(xerr.InvalidArgument, "gift configs are required")
}
batchCommands := make([]resourcedomain.GiftConfigCommand, 0, len(commands))
for _, command := range commands {
command.AppCode = appCode
if command.OperatorUserID == 0 {
command.OperatorUserID = operatorUserID
}
batchCommands = append(batchCommands, command)
}
return s.repository.BatchCreateGiftConfigs(ctx, batchCommands)
}
func (s *Service) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) { func (s *Service) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) {
if s.repository == nil { if s.repository == nil {
return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")

View File

@ -3694,6 +3694,195 @@ func TestDeleteGiftConfigKeepsResourceData(t *testing.T) {
} }
} }
func TestDeleteResourceHidesFromDefaultList(t *testing.T) {
repository := mysqltest.NewRepository(t)
svc := walletservice.New(repository)
ctx := context.Background()
resource, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{
ResourceCode: "delete_resource_hidden",
ResourceType: resourcedomain.TypeBadge,
Name: "Delete Resource Hidden",
Status: resourcedomain.StatusActive,
Grantable: true,
GrantStrategy: resourcedomain.GrantStrategyNewEntitlement,
UsageScopes: []string{"badge"},
OperatorUserID: 91001,
})
if err != nil {
t.Fatalf("create resource failed: %v", err)
}
deleted, err := svc.DeleteResource(ctx, resourcedomain.StatusCommand{
ID: resource.ResourceID,
OperatorUserID: 91002,
})
if err != nil {
t.Fatalf("delete resource failed: %v", err)
}
if deleted.ResourceID != resource.ResourceID || deleted.Status != resourcedomain.StatusDeleted {
t.Fatalf("deleted resource snapshot mismatch: %+v", deleted)
}
if got := repository.CountRows("resources", "resource_id = ? AND status = ?", resource.ResourceID, resourcedomain.StatusDeleted); got != 1 {
t.Fatalf("resource should be soft deleted, got %d", got)
}
if got := repository.CountRows("resources", "resource_code = ?", "delete_resource_hidden"); got != 0 {
t.Fatalf("deleted resource should release resource_code uniqueness, got %d", got)
}
items, total, err := svc.ListResources(ctx, resourcedomain.ListResourcesQuery{
Keyword: "delete_resource_hidden",
Page: 1,
PageSize: 10,
})
if err != nil {
t.Fatalf("list resources failed: %v", err)
}
if total != 0 || len(items) != 0 {
t.Fatalf("deleted resource should be hidden from default list: total=%d items=%+v", total, items)
}
items, total, err = svc.ListResources(ctx, resourcedomain.ListResourcesQuery{
Keyword: "delete_resource_hidden",
Status: resourcedomain.StatusDeleted,
Page: 1,
PageSize: 10,
})
if err != nil {
t.Fatalf("list deleted status resources failed: %v", err)
}
if total != 0 || len(items) != 0 {
t.Fatalf("deleted resource should not be exposed by status filter: total=%d items=%+v", total, items)
}
loaded, err := svc.GetResource(ctx, "", resource.ResourceID)
if err != nil {
t.Fatalf("deleted resource should remain readable for historical references: %v", err)
}
if loaded.Status != resourcedomain.StatusDeleted {
t.Fatalf("loaded deleted resource status mismatch: %+v", loaded)
}
recreated, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{
ResourceCode: "delete_resource_hidden",
ResourceType: resourcedomain.TypeBadge,
Name: "Recreated Resource",
Status: resourcedomain.StatusActive,
Grantable: true,
GrantStrategy: resourcedomain.GrantStrategyNewEntitlement,
UsageScopes: []string{"badge"},
OperatorUserID: 91003,
})
if err != nil {
t.Fatalf("recreate resource with deleted code failed: %v", err)
}
if recreated.ResourceID == resource.ResourceID {
t.Fatalf("recreated resource should be a new row: old=%d new=%d", resource.ResourceID, recreated.ResourceID)
}
}
func TestDeleteGiftResourceHidesGiftConfigList(t *testing.T) {
repository := mysqltest.NewRepository(t)
svc := walletservice.New(repository)
ctx := context.Background()
resource, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{
ResourceCode: "delete_gift_resource_hidden",
ResourceType: resourcedomain.TypeGift,
Name: "Delete Gift Resource Hidden",
Status: resourcedomain.StatusActive,
Grantable: true,
GrantStrategy: resourcedomain.GrantStrategyIncreaseQuantity,
UsageScopes: []string{"gift"},
OperatorUserID: 91101,
})
if err != nil {
t.Fatalf("create gift resource failed: %v", err)
}
if _, err := svc.CreateGiftConfig(ctx, resourcedomain.GiftConfigCommand{
GiftID: "delete-resource-hide-gift",
ResourceID: resource.ResourceID,
Status: resourcedomain.StatusActive,
Name: "Delete Resource Hide Gift",
PriceVersion: "default",
CoinPrice: 10,
HeatValue: 10,
OperatorUserID: 91101,
RegionIDs: []int64{0},
}); err != nil {
t.Fatalf("create gift config failed: %v", err)
}
if _, err := svc.DeleteResource(ctx, resourcedomain.StatusCommand{
ID: resource.ResourceID,
OperatorUserID: 91102,
}); err != nil {
t.Fatalf("delete gift resource failed: %v", err)
}
gifts, total, err := svc.ListGiftConfigs(ctx, resourcedomain.ListGiftConfigsQuery{
Keyword: "delete-resource-hide-gift",
Page: 1,
PageSize: 10,
})
if err != nil {
t.Fatalf("list gift configs failed: %v", err)
}
if total != 0 || len(gifts) != 0 {
t.Fatalf("gift config should be hidden when resource is deleted: total=%d gifts=%+v", total, gifts)
}
if got := repository.CountRows("gift_configs", "gift_id = ?", "delete-resource-hide-gift"); got != 1 {
t.Fatalf("soft-deleting resource should keep gift config row for history, got %d", got)
}
}
func TestBatchCreateGiftConfigsRollsBackOnFailure(t *testing.T) {
repository := mysqltest.NewRepository(t)
svc := walletservice.New(repository)
ctx := context.Background()
resource, err := svc.CreateResource(ctx, resourcedomain.ResourceCommand{
ResourceCode: "batch_gift_valid_resource",
ResourceType: resourcedomain.TypeGift,
Name: "Batch Gift Valid Resource",
Status: resourcedomain.StatusActive,
Grantable: true,
GrantStrategy: resourcedomain.GrantStrategyIncreaseQuantity,
UsageScopes: []string{"gift"},
OperatorUserID: 91201,
})
if err != nil {
t.Fatalf("create gift resource failed: %v", err)
}
_, err = svc.BatchCreateGiftConfigs(ctx, "", []resourcedomain.GiftConfigCommand{
{
GiftID: "batch-create-rollback-ok",
ResourceID: resource.ResourceID,
Status: resourcedomain.StatusActive,
Name: "Batch Create Rollback OK",
PriceVersion: "default",
CoinPrice: 10,
HeatValue: 10,
OperatorUserID: 91201,
RegionIDs: []int64{0},
},
{
GiftID: "batch-create-rollback-bad",
ResourceID: 99999999,
Status: resourcedomain.StatusActive,
Name: "Batch Create Rollback Bad",
PriceVersion: "default",
CoinPrice: 10,
HeatValue: 10,
OperatorUserID: 91201,
RegionIDs: []int64{0},
},
}, 91201)
if err == nil {
t.Fatalf("batch create should fail when any item is invalid")
}
if got := repository.CountRows("gift_configs", "gift_id IN (?, ?)", "batch-create-rollback-ok", "batch-create-rollback-bad"); got != 0 {
t.Fatalf("batch create should roll back all gift rows, got %d", got)
}
if got := repository.CountRows("wallet_gift_prices", "gift_id IN (?, ?)", "batch-create-rollback-ok", "batch-create-rollback-bad"); got != 0 {
t.Fatalf("batch create should roll back all gift prices, got %d", got)
}
}
func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) { func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
repository := mysqltest.NewRepository(t) repository := mysqltest.NewRepository(t)
svc := walletservice.New(repository) svc := walletservice.New(repository)

View File

@ -305,7 +305,7 @@ func giftConfigSelectSQL() string {
} }
func giftConfigWhereSQL(query resourcedomain.ListGiftConfigsQuery) (string, []any) { func giftConfigWhereSQL(query resourcedomain.ListGiftConfigsQuery) (string, []any) {
where := `WHERE gc.app_code = ?` where := `WHERE gc.app_code = ? AND r.status <> 'deleted'`
args := []any{query.AppCode} args := []any{query.AppCode}
if query.ActiveOnly { if query.ActiveOnly {
where += ` AND gc.status = 'active' AND r.status = 'active' AND r.resource_type = 'gift' where += ` AND gc.status = 'active' AND r.status = 'active' AND r.resource_type = 'gift'

View File

@ -2,6 +2,7 @@ package mysql
import ( import (
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
@ -15,6 +16,41 @@ func (r *Repository) CreateGiftConfig(ctx context.Context, command resourcedomai
return r.upsertGiftConfig(ctx, command, false) return r.upsertGiftConfig(ctx, command, false)
} }
func (r *Repository) BatchCreateGiftConfigs(ctx context.Context, commands []resourcedomain.GiftConfigCommand) ([]resourcedomain.GiftConfig, error) {
if r == nil || r.db == nil {
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
if len(commands) == 0 {
return nil, xerr.New(xerr.InvalidArgument, "gift configs are required")
}
ctx = contextWithCommandApp(ctx, commands[0].AppCode)
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback() }()
nowMs := time.Now().UnixMilli()
gifts := make([]resourcedomain.GiftConfig, 0, len(commands))
events := make([]walletOutboxEvent, 0, len(commands))
for _, command := range commands {
command.AppCode = appcode.FromContext(ctx)
gift, err := r.createGiftConfigTx(ctx, tx, command, nowMs)
if err != nil {
return nil, err
}
gifts = append(gifts, gift)
events = append(events, resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:config:%d", command.GiftID, nowMs), 0, command.ResourceID, command, nowMs))
}
if err := r.insertWalletOutbox(ctx, tx, events); err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return gifts, nil
}
func (r *Repository) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) { func (r *Repository) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) {
return r.upsertGiftConfig(ctx, command, true) return r.upsertGiftConfig(ctx, command, true)
} }
@ -118,52 +154,22 @@ func (r *Repository) upsertGiftConfig(ctx context.Context, command resourcedomai
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
resource, err := r.getResourceForUpdate(ctx, tx, command.ResourceID)
if err != nil {
return resourcedomain.GiftConfig{}, err
}
if resource.ResourceType != resourcedomain.TypeGift {
return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift config must select gift resource")
}
command = applyResourcePricingToGiftCommand(command, resource)
if resourcedomain.NormalizePriceType(resource.PriceType) == "" && command.CoinPrice <= 0 {
return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift price is invalid")
}
if err := validateGiftConfigCommand(command); err != nil {
return resourcedomain.GiftConfig{}, err
}
if command.Status == resourcedomain.StatusActive && resource.Status != resourcedomain.StatusActive {
return resourcedomain.GiftConfig{}, xerr.New(xerr.Conflict, "gift resource is disabled")
}
if err := r.ensureDefaultGiftTypeConfigs(ctx, tx); err != nil {
return resourcedomain.GiftConfig{}, err
}
if ok, err := r.giftTypeConfigExistsTx(ctx, tx, command.GiftTypeCode); err != nil {
return resourcedomain.GiftConfig{}, err
} else if !ok {
return resourcedomain.GiftConfig{}, xerr.New(xerr.InvalidArgument, "gift type is not configured")
}
nowMs := time.Now().UnixMilli() nowMs := time.Now().UnixMilli()
var gift resourcedomain.GiftConfig
presentation := giftPresentationWithCPRelationType(command.PresentationJSON, command.CPRelationType)
effectTypesJSON, err := json.Marshal(command.EffectTypes)
if err != nil {
return resourcedomain.GiftConfig{}, err
}
if !update { if !update {
if _, err := tx.ExecContext(ctx, ` gift, err = r.createGiftConfigTx(ctx, tx, command, nowMs)
INSERT INTO gift_configs ( if err != nil {
app_code, gift_id, resource_id, status, name, sort_order, presentation_json,
gift_type_code, effective_from_ms, effective_to_ms, effect_types_json,
created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
command.AppCode, command.GiftID, command.ResourceID, command.Status, command.Name, command.SortOrder, presentation,
command.GiftTypeCode, command.EffectiveFromMS, command.EffectiveToMS, string(effectTypesJSON),
command.OperatorUserID, command.OperatorUserID, nowMs, nowMs,
); err != nil {
return resourcedomain.GiftConfig{}, err return resourcedomain.GiftConfig{}, err
} }
} else { } else {
if err := r.prepareGiftConfigCommandTx(ctx, tx, &command); err != nil {
return resourcedomain.GiftConfig{}, err
}
presentation := giftPresentationWithCPRelationType(command.PresentationJSON, command.CPRelationType)
effectTypesJSON, err := json.Marshal(command.EffectTypes)
if err != nil {
return resourcedomain.GiftConfig{}, err
}
result, err := tx.ExecContext(ctx, ` result, err := tx.ExecContext(ctx, `
UPDATE gift_configs UPDATE gift_configs
SET resource_id = ?, status = ?, name = ?, sort_order = ?, presentation_json = ?, SET resource_id = ?, status = ?, name = ?, sort_order = ?, presentation_json = ?,
@ -182,12 +188,12 @@ func (r *Repository) upsertGiftConfig(ctx context.Context, command resourcedomai
} else if affected == 0 { } else if affected == 0 {
return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found") return resourcedomain.GiftConfig{}, xerr.New(xerr.NotFound, "gift config not found")
} }
} if err := r.upsertGiftPriceTx(ctx, tx, command, nowMs); err != nil {
if err := r.upsertGiftPriceTx(ctx, tx, command, nowMs); err != nil { return resourcedomain.GiftConfig{}, err
return resourcedomain.GiftConfig{}, err }
} if err := r.replaceGiftConfigRegionsTx(ctx, tx, command.GiftID, command.RegionIDs, nowMs); err != nil {
if err := r.replaceGiftConfigRegionsTx(ctx, tx, command.GiftID, command.RegionIDs, nowMs); err != nil { return resourcedomain.GiftConfig{}, err
return resourcedomain.GiftConfig{}, err }
} }
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:config:%d", command.GiftID, nowMs), 0, command.ResourceID, command, nowMs), resourceOutboxEvent("GiftConfigChanged", fmt.Sprintf("gift:%s:config:%d", command.GiftID, nowMs), 0, command.ResourceID, command, nowMs),
@ -197,5 +203,67 @@ func (r *Repository) upsertGiftConfig(ctx context.Context, command resourcedomai
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return resourcedomain.GiftConfig{}, err return resourcedomain.GiftConfig{}, err
} }
return r.getGiftConfig(ctx, command.GiftID) if update {
return r.getGiftConfig(ctx, command.GiftID)
}
return gift, nil
}
func (r *Repository) createGiftConfigTx(ctx context.Context, tx *sql.Tx, command resourcedomain.GiftConfigCommand, nowMs int64) (resourcedomain.GiftConfig, error) {
if err := r.prepareGiftConfigCommandTx(ctx, tx, &command); err != nil {
return resourcedomain.GiftConfig{}, err
}
presentation := giftPresentationWithCPRelationType(command.PresentationJSON, command.CPRelationType)
effectTypesJSON, err := json.Marshal(command.EffectTypes)
if err != nil {
return resourcedomain.GiftConfig{}, err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO gift_configs (
app_code, gift_id, resource_id, status, name, sort_order, presentation_json,
gift_type_code, effective_from_ms, effective_to_ms, effect_types_json,
created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
command.AppCode, command.GiftID, command.ResourceID, command.Status, command.Name, command.SortOrder, presentation,
command.GiftTypeCode, command.EffectiveFromMS, command.EffectiveToMS, string(effectTypesJSON),
command.OperatorUserID, command.OperatorUserID, nowMs, nowMs,
); err != nil {
return resourcedomain.GiftConfig{}, err
}
if err := r.upsertGiftPriceTx(ctx, tx, command, nowMs); err != nil {
return resourcedomain.GiftConfig{}, err
}
if err := r.replaceGiftConfigRegionsTx(ctx, tx, command.GiftID, command.RegionIDs, nowMs); err != nil {
return resourcedomain.GiftConfig{}, err
}
return r.getGiftConfigForUpdateTx(ctx, tx, command.GiftID)
}
func (r *Repository) prepareGiftConfigCommandTx(ctx context.Context, tx *sql.Tx, command *resourcedomain.GiftConfigCommand) error {
resource, err := r.getResourceForUpdate(ctx, tx, command.ResourceID)
if err != nil {
return err
}
if resource.ResourceType != resourcedomain.TypeGift {
return xerr.New(xerr.InvalidArgument, "gift config must select gift resource")
}
*command = applyResourcePricingToGiftCommand(*command, resource)
if resourcedomain.NormalizePriceType(resource.PriceType) == "" && command.CoinPrice <= 0 {
return xerr.New(xerr.InvalidArgument, "gift price is invalid")
}
if err := validateGiftConfigCommand(*command); err != nil {
return err
}
if command.Status == resourcedomain.StatusActive && resource.Status != resourcedomain.StatusActive {
return xerr.New(xerr.Conflict, "gift resource is disabled")
}
if err := r.ensureDefaultGiftTypeConfigs(ctx, tx); err != nil {
return err
}
if ok, err := r.giftTypeConfigExistsTx(ctx, tx, command.GiftTypeCode); err != nil {
return err
} else if !ok {
return xerr.New(xerr.InvalidArgument, "gift type is not configured")
}
return nil
} }

View File

@ -342,6 +342,75 @@ func (r *Repository) SetResourceStatus(ctx context.Context, command resourcedoma
return resource, nil return resource, nil
} }
func (r *Repository) DeleteResource(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error) {
resources, err := r.BatchDeleteResources(ctx, resourcedomain.BatchDeleteResourcesCommand{
AppCode: command.AppCode,
ResourceIDs: []int64{command.ID},
OperatorUserID: command.OperatorUserID,
})
if err != nil {
return resourcedomain.Resource{}, err
}
if len(resources) == 0 {
return resourcedomain.Resource{}, xerr.New(xerr.NotFound, "resource not found")
}
return resources[0], nil
}
func (r *Repository) BatchDeleteResources(ctx context.Context, command resourcedomain.BatchDeleteResourcesCommand) ([]resourcedomain.Resource, error) {
if r == nil || r.db == nil {
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
ctx = contextWithCommandApp(ctx, command.AppCode)
resourceIDs := normalizeResourceDeleteIDs(command.ResourceIDs)
if len(resourceIDs) == 0 {
return nil, xerr.New(xerr.InvalidArgument, "resource_ids are required")
}
nowMs := time.Now().UnixMilli()
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback() }()
deletedResources := make([]resourcedomain.Resource, 0, len(resourceIDs))
events := make([]walletOutboxEvent, 0, len(resourceIDs))
for _, resourceID := range resourceIDs {
resource, err := r.getResourceForUpdate(ctx, tx, resourceID)
if err != nil {
return nil, err
}
if resource.Status == resourcedomain.StatusDeleted {
return nil, xerr.New(xerr.NotFound, "resource not found")
}
result, err := tx.ExecContext(ctx,
`UPDATE resources SET resource_code = ?, status = ?, updated_by_user_id = ?, updated_at_ms = ? WHERE app_code = ? AND resource_id = ? AND status <> ?`,
deletedResourceCode(resource.ResourceCode, resourceID), resourcedomain.StatusDeleted, command.OperatorUserID, nowMs, appcode.FromContext(ctx), resourceID, resourcedomain.StatusDeleted,
)
if err != nil {
return nil, err
}
if affected, err := result.RowsAffected(); err != nil {
return nil, err
} else if affected == 0 {
return nil, xerr.New(xerr.NotFound, "resource not found")
}
resource.Status = resourcedomain.StatusDeleted
resource.UpdatedByUserID = command.OperatorUserID
resource.UpdatedAtMS = nowMs
deletedResources = append(deletedResources, resource)
events = append(events, resourceOutboxEvent("ResourceChanged", fmt.Sprintf("resource:%d:delete:%d", resourceID, nowMs), 0, resourceID, resource, nowMs))
}
if err := r.insertWalletOutbox(ctx, tx, events); err != nil {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return deletedResources, nil
}
func (r *Repository) getResourceForUpdate(ctx context.Context, tx *sql.Tx, resourceID int64) (resourcedomain.Resource, error) { func (r *Repository) getResourceForUpdate(ctx context.Context, tx *sql.Tx, resourceID int64) (resourcedomain.Resource, error) {
row := tx.QueryRowContext(ctx, resourceSelectSQL()+` WHERE app_code = ? AND resource_id = ? FOR UPDATE`, appcode.FromContext(ctx), resourceID) row := tx.QueryRowContext(ctx, resourceSelectSQL()+` WHERE app_code = ? AND resource_id = ? FOR UPDATE`, appcode.FromContext(ctx), resourceID)
resource, err := scanResource(row) resource, err := scanResource(row)
@ -418,7 +487,7 @@ func nullableResourceColumnsWithAlias(alias string) string {
} }
func resourceWhereSQL(query resourcedomain.ListResourcesQuery) (string, []any) { func resourceWhereSQL(query resourcedomain.ListResourcesQuery) (string, []any) {
where := `WHERE app_code = ?` where := `WHERE app_code = ? AND status <> 'deleted'`
args := []any{query.AppCode} args := []any{query.AppCode}
if query.ManagerGrantOnly { if query.ManagerGrantOnly {
where += ` AND status = 'active' AND grantable = TRUE AND manager_grant_enabled = TRUE` where += ` AND status = 'active' AND grantable = TRUE AND manager_grant_enabled = TRUE`
@ -440,6 +509,35 @@ func resourceWhereSQL(query resourcedomain.ListResourcesQuery) (string, []any) {
return where, args return where, args
} }
func normalizeResourceDeleteIDs(resourceIDs []int64) []int64 {
seen := make(map[int64]struct{}, len(resourceIDs))
normalized := make([]int64, 0, len(resourceIDs))
for _, resourceID := range resourceIDs {
if resourceID <= 0 {
continue
}
if _, ok := seen[resourceID]; ok {
continue
}
seen[resourceID] = struct{}{}
normalized = append(normalized, resourceID)
}
return normalized
}
func deletedResourceCode(resourceCode string, resourceID int64) string {
const maxResourceCodeLength = 96
suffix := fmt.Sprintf("__deleted_%d", resourceID)
if len(resourceCode)+len(suffix) <= maxResourceCodeLength {
return resourceCode + suffix
}
keepLength := maxResourceCodeLength - len(suffix)
if keepLength <= 0 {
return suffix[len(suffix)-maxResourceCodeLength:]
}
return resourceCode[:keepLength] + suffix
}
func normalizeResourceListQuery(query resourcedomain.ListResourcesQuery) resourcedomain.ListResourcesQuery { func normalizeResourceListQuery(query resourcedomain.ListResourcesQuery) resourcedomain.ListResourcesQuery {
query.ResourceType = resourcedomain.NormalizeResourceType(query.ResourceType) query.ResourceType = resourcedomain.NormalizeResourceType(query.ResourceType)
if query.ResourceType != "" && !resourcedomain.ValidResourceType(query.ResourceType) { if query.ResourceType != "" && !resourcedomain.ValidResourceType(query.ResourceType) {

View File

@ -65,6 +65,34 @@ func (s *Server) SetResourceStatus(ctx context.Context, req *walletv1.SetResourc
return &walletv1.ResourceResponse{Resource: resourceToProto(resource)}, nil return &walletv1.ResourceResponse{Resource: resourceToProto(resource)}, nil
} }
func (s *Server) DeleteResource(ctx context.Context, req *walletv1.DeleteResourceRequest) (*walletv1.ResourceResponse, error) {
resource, err := s.svc.DeleteResource(ctx, resourcedomain.StatusCommand{
AppCode: req.GetAppCode(),
ID: req.GetResourceId(),
OperatorUserID: req.GetOperatorUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.ResourceResponse{Resource: resourceToProto(resource)}, nil
}
func (s *Server) BatchDeleteResources(ctx context.Context, req *walletv1.BatchDeleteResourcesRequest) (*walletv1.BatchDeleteResourcesResponse, error) {
resources, err := s.svc.BatchDeleteResources(ctx, resourcedomain.BatchDeleteResourcesCommand{
AppCode: req.GetAppCode(),
ResourceIDs: req.GetResourceIds(),
OperatorUserID: req.GetOperatorUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.BatchDeleteResourcesResponse{Resources: make([]*walletv1.Resource, 0, len(resources))}
for _, resource := range resources {
resp.Resources = append(resp.Resources, resourceToProto(resource))
}
return resp, nil
}
func (s *Server) ListResourceGroups(ctx context.Context, req *walletv1.ListResourceGroupsRequest) (*walletv1.ListResourceGroupsResponse, error) { func (s *Server) ListResourceGroups(ctx context.Context, req *walletv1.ListResourceGroupsRequest) (*walletv1.ListResourceGroupsResponse, error) {
items, total, err := s.svc.ListResourceGroups(ctx, resourcedomain.ListResourceGroupsQuery{ items, total, err := s.svc.ListResourceGroups(ctx, resourcedomain.ListResourceGroupsQuery{
AppCode: req.GetAppCode(), AppCode: req.GetAppCode(),
@ -175,6 +203,27 @@ func (s *Server) CreateGiftConfig(ctx context.Context, req *walletv1.CreateGiftC
return &walletv1.GiftConfigResponse{Gift: giftConfigToProto(gift)}, nil return &walletv1.GiftConfigResponse{Gift: giftConfigToProto(gift)}, nil
} }
func (s *Server) BatchCreateGiftConfigs(ctx context.Context, req *walletv1.BatchCreateGiftConfigsRequest) (*walletv1.BatchCreateGiftConfigsResponse, error) {
commands := make([]resourcedomain.GiftConfigCommand, 0, len(req.GetItems()))
for _, item := range req.GetItems() {
command := giftConfigCommandFromCreate(item)
command.AppCode = req.GetAppCode()
if command.OperatorUserID == 0 {
command.OperatorUserID = req.GetOperatorUserId()
}
commands = append(commands, command)
}
gifts, err := s.svc.BatchCreateGiftConfigs(ctx, req.GetAppCode(), commands, req.GetOperatorUserId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.BatchCreateGiftConfigsResponse{Gifts: make([]*walletv1.GiftConfig, 0, len(gifts))}
for _, gift := range gifts {
resp.Gifts = append(resp.Gifts, giftConfigToProto(gift))
}
return resp, nil
}
func (s *Server) UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) { func (s *Server) UpdateGiftConfig(ctx context.Context, req *walletv1.UpdateGiftConfigRequest) (*walletv1.GiftConfigResponse, error) {
gift, err := s.svc.UpdateGiftConfig(ctx, giftConfigCommandFromUpdate(req)) gift, err := s.svc.UpdateGiftConfig(ctx, giftConfigCommandFromUpdate(req))
if err != nil { if err != nil {