From 2bb7885a9a5007e8abfc2e0d9a32939c27751ca6 Mon Sep 17 00:00:00 2001 From: yuizho Date: Sat, 14 May 2022 12:18:46 +0900 Subject: [PATCH] impl kick operation --- .../room-api/resolvers/Mutation.kick.req.vtl | 50 +++++++++++++++++++ room/appsync/room-api/schema.graphql | 3 ++ room/lambda/room-rmu/model/operation.go | 33 +++++++----- room/lambda/room-rmu/model/room_factory.go | 2 +- room/lambda/room-rmu/service/room_service.go | 28 +++++++++++ room/lib/room-stack.ts | 8 +++ 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 room/appsync/room-api/resolvers/Mutation.kick.req.vtl diff --git a/room/appsync/room-api/resolvers/Mutation.kick.req.vtl b/room/appsync/room-api/resolvers/Mutation.kick.req.vtl new file mode 100644 index 0000000..ec96582 --- /dev/null +++ b/room/appsync/room-api/resolvers/Mutation.kick.req.vtl @@ -0,0 +1,50 @@ +#if($util.isNullOrEmpty($ctx.args.room_id)) + $util.error("null or empty", "room_id") +#end +#if($ctx.args.room_id.length() > 30) + $util.error("unexpected length (too long)", "room_id") +#end +#if(!$util.matches("^[0-9a-zA-Z]+$", $ctx.args.room_id)) + $util.error("unexpected pattern", "room_id") +#end + +#if($util.isNullOrEmpty($ctx.args.user_id)) + $util.error("null or empty", "user_id") +#end +#if($ctx.args.user_id.length() > 30) + $util.error("unexpected length (too long)", "user_id") +#end +#if(!$util.matches("^[0-9a-zA-Z]+$", $ctx.args.user_id)) + $util.error("unexpected pattern", "user_id") +#end + +#if($util.isNullOrEmpty($ctx.args.kicked_user_id)) + $util.error("null or empty", "kicked_user_id") +#end +#if($ctx.args.kicked_user_id.length() > 30) + $util.error("unexpected length (too long)", "kicked_user_id") +#end +#if(!$util.matches("^[0-9a-zA-Z]+$", $ctx.args.kicked_user_id)) + $util.error("unexpected pattern", "kicked_user_id") +#end + +{ + "version" : "2017-02-28", + "operation" : "PutItem", + "key" : { + "event_id": $util.dynamodb.toDynamoDBJson($util.autoUlid()), + }, + "attributeValues" : { + "room_id": $util.dynamodb.toDynamoDBJson($ctx.args.room_id), + "user_id": $util.dynamodb.toDynamoDBJson($ctx.args.user_id), + "op_type": $util.dynamodb.toDynamoDBJson("KICK"), + "operated_at": $util.dynamodb.toDynamoDBJson($util.time.nowISO8601()), + "kicked_user_id": $util.dynamodb.toDynamoDBJson($ctx.args.kicked_user_id), + }, + "condition": { + "expression": "attribute_not_exists(#event_id)", + "expressionNames": { + "#event_id": "event_id", + }, + }, +} \ No newline at end of file diff --git a/room/appsync/room-api/schema.graphql b/room/appsync/room-api/schema.graphql index 4afbbb0..dd265f0 100644 --- a/room/appsync/room-api/schema.graphql +++ b/room/appsync/room-api/schema.graphql @@ -6,6 +6,8 @@ @aws_api_key refreshTable(room_id: String!, user_id: String!): Operation @aws_api_key heartbeat(room_id: String!, user_id: String!): Operation @aws_api_key + kick(room_id: String!, user_id: String!, kicked_user_id: String!): Operation + @aws_api_key updatePoker( room_id: String! user_id: String! @@ -42,6 +44,7 @@ enum OperationType { PICK REFRESH_TABLE HEARTBEAT + KICK } type Room @aws_api_key @aws_iam { diff --git a/room/lambda/room-rmu/model/operation.go b/room/lambda/room-rmu/model/operation.go index db68e93..c66c922 100644 --- a/room/lambda/room-rmu/model/operation.go +++ b/room/lambda/room-rmu/model/operation.go @@ -17,12 +17,13 @@ const ( Pick = OpType("PICK") RefreshTable = OpType("REFRESH_TABLE") Heartbeat = OpType("HEARTBEAT") + Kick = OpType("KICK") ) func NewOpType(s string) (OpType, error) { opType := OpType(s) switch opType { - case OpenRoom, Join, Leave, Pick, RefreshTable, Heartbeat: + case OpenRoom, Join, Leave, Pick, RefreshTable, Heartbeat, Kick: return opType, nil default: return "", fmt.Errorf("unexpected op_type string: %s", s) @@ -34,12 +35,13 @@ func (opType OpType) String() string { } type Operation struct { - EventId string `dynamodbav:"event_id" json:"event_id"` - RoomId string `dynamodbav:"room_id" json:"room_id"` - OpType OpType `dynamodbav:"op_type" json:"op_type"` - UserId string `dynamodbav:"user_id" json:"user_id"` - OperatedAt string `dynamodbav:"operated_at" json:"operated_at"` - PickedCard string `dynamodbav:"picked_card" json:"picked_card"` + EventId string `dynamodbav:"event_id" json:"event_id"` + RoomId string `dynamodbav:"room_id" json:"room_id"` + OpType OpType `dynamodbav:"op_type" json:"op_type"` + UserId string `dynamodbav:"user_id" json:"user_id"` + OperatedAt string `dynamodbav:"operated_at" json:"operated_at"` + PickedCard string `dynamodbav:"picked_card" json:"picked_card"` + KickedUserId string `dynamodbav:"kicked_user_id" json:"kicked_user_id"` } func NewOperation(attrs map[string]events.DynamoDBAttributeValue) (*Operation, error) { @@ -81,6 +83,10 @@ func NewOperation(attrs map[string]events.DynamoDBAttributeValue) (*Operation, e return nil, errors.New("invalid operated_at") } + if m, _ := regexp.MatchString(`^[0-9a-zA-Z\-]+$`, attrs["kicked_user_id"].String()); !m { + return nil, errors.New("invalid kicked_user_id") + } + pickedCard := "" if !attrs["picked_card"].IsNull() { if m, _ := regexp.MatchString(`^[0-9a-zA-Z]*$`, attrs["picked_card"].String()); !m { @@ -90,11 +96,12 @@ func NewOperation(attrs map[string]events.DynamoDBAttributeValue) (*Operation, e } return &Operation{ - EventId: attrs["event_id"].String(), - RoomId: attrs["room_id"].String(), - OpType: opType, - UserId: attrs["user_id"].String(), - OperatedAt: attrs["operated_at"].String(), - PickedCard: pickedCard, + EventId: attrs["event_id"].String(), + RoomId: attrs["room_id"].String(), + OpType: opType, + UserId: attrs["user_id"].String(), + OperatedAt: attrs["operated_at"].String(), + PickedCard: pickedCard, + KickedUserId: attrs["kicked_user_id"].String(), }, nil } diff --git a/room/lambda/room-rmu/model/room_factory.go b/room/lambda/room-rmu/model/room_factory.go index f52559e..19a35de 100644 --- a/room/lambda/room-rmu/model/room_factory.go +++ b/room/lambda/room-rmu/model/room_factory.go @@ -27,7 +27,7 @@ func createStatus(opType OpType) (Status, error) { switch opType { case OpenRoom, Join, RefreshTable: return NewStatus("CHOOSING") - case Leave: + case Leave, Kick: return NewStatus("LEAVED") case Pick: return NewStatus("CHOSEN") diff --git a/room/lambda/room-rmu/service/room_service.go b/room/lambda/room-rmu/service/room_service.go index 35c00d8..0436fe0 100644 --- a/room/lambda/room-rmu/service/room_service.go +++ b/room/lambda/room-rmu/service/room_service.go @@ -42,6 +42,8 @@ func (service *RoomService) SaveRoom(context context.Context, attrs map[string]e return service.refreshPokerTable(context, operation) case model.Heartbeat: return service.updateOperatedAt(context, operation) + case model.Kick: + return service.kickUser(context, operation) default: return fmt.Errorf("unreachable error") } @@ -148,6 +150,32 @@ func (service *RoomService) updateOperatedAt(context context.Context, operation return service.repository.UpdateActiveUserOperatedAt(context, operation.RoomId, operation.UserId, operation.OperatedAt) } +func (service *RoomService) kickUser(context context.Context, operation *model.Operation) error { + rooms, err := service.repository.FindActiveUsers(context, operation.RoomId) + if err != nil { + return err + } + + if !isProperUser(rooms, operation.UserId) { + return fmt.Errorf("passed user id is not active: %s", operation.UserId) + } + + reqId, err := util.GetAWSRequestId(context) + if err != nil { + return err + } + + logger.Infof("%s Room to kick %s by %s", reqId, operation.KickedUserId, operation.UserId) + + return service.updateUserState(context, &model.Operation{ + EventId: operation.EventId, + RoomId: operation.RoomId, + OpType: model.OpType("LEAVE"), + UserId: operation.KickedUserId, + OperatedAt: operation.OperatedAt, + }) +} + func isProperUser(rooms *[]model.Room, userId string) bool { for _, room := range *rooms { if room.UserId == userId { diff --git a/room/lib/room-stack.ts b/room/lib/room-stack.ts index b484dd0..d6f5e8a 100644 --- a/room/lib/room-stack.ts +++ b/room/lib/room-stack.ts @@ -115,6 +115,14 @@ export class RoomStack extends Stack { ), responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), }); + operationDataSource.createResolver({ + typeName: "Mutation", + fieldName: "kick", + requestMappingTemplate: appsync.MappingTemplate.fromFile( + "appsync/room-api/resolvers/Mutation.kick.req.vtl" + ), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), + }); const roomDataSource = roomAPI.addDynamoDbDataSource("room", roomTable); const PokerDataSource = roomAPI.addNoneDataSource("poker"); // Define resolvers