From 0b04aa3a335bff628f922fdf0441139c5546229c Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 26 Sep 2024 10:54:46 +0200 Subject: [PATCH 01/42] ward conflict --- libs/common/errors.go | 29 +++++++++ libs/hwutil/parse.go | 11 ++++ services/tasks-svc/internal/util/conflict.go | 26 ++++++++ services/tasks-svc/internal/ward/ward.go | 60 +++++++++++++++++-- services/tasks-svc/repos/ward_repo.sql | 12 +++- .../repos/ward_repo/ward_repo.sql.go | 26 ++++++-- services/tasks-svc/stories/WardCRUD_test.go | 45 ++++++++++++++ 7 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 libs/common/errors.go create mode 100644 services/tasks-svc/internal/util/conflict.go diff --git a/libs/common/errors.go b/libs/common/errors.go new file mode 100644 index 000000000..0f628ecfd --- /dev/null +++ b/libs/common/errors.go @@ -0,0 +1,29 @@ +package common + +import ( + "common/locale" + "context" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/codes" + "hwlocale" +) + +// SingleInvalidFieldError builds a NewStatusError +func SingleInvalidFieldError(ctx context.Context, field, msg string, locale hwlocale.Locale) error { + return NewStatusError(ctx, + codes.InvalidArgument, + msg, + locale, + &errdetails.BadRequest{ + FieldViolations: []*errdetails.BadRequest_FieldViolation{ + { + Field: field, + Description: hwlocale.Localize(ctx, locale), + }, + }}, + ) +} + +func UnparsableConsistencyError(ctx context.Context, field string) error { + return SingleInvalidFieldError(ctx, field, "consistency not parsable", locale.InvalidFieldError(ctx)) +} diff --git a/libs/hwutil/parse.go b/libs/hwutil/parse.go index 952ab5400..f1d56c6e1 100644 --- a/libs/hwutil/parse.go +++ b/libs/hwutil/parse.go @@ -131,3 +131,14 @@ type JSONAble interface { type MapAble interface { ToMap() map[string]interface{} } + +func ParseConsistency(consistencyStr *string) (consistency *uint64, success bool) { + if consistencyStr == nil { + return nil, true + } + if c, err := strconv.ParseUint(*consistencyStr, 10, 64); err != nil { + return nil, false + } else { + return &c, true + } +} diff --git a/services/tasks-svc/internal/util/conflict.go b/services/tasks-svc/internal/util/conflict.go new file mode 100644 index 000000000..9bc88dd9e --- /dev/null +++ b/services/tasks-svc/internal/util/conflict.go @@ -0,0 +1,26 @@ +package util + +import ( + "fmt" + commonpb "gen/libs/common/v1" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +// AttributeConflict is a constructor for commonpb.AttributeConflicts +// I'd love to move this somewhere else, but I also don't want common to depend on gen (and thus hwdb, hwes, ...) +func AttributeConflict(is, want proto.Message) (*commonpb.AttributeConflict, error) { + wantAny, err := anypb.New(want) + if err != nil { + return nil, fmt.Errorf("AttributeConflict could not marshal want: %w", err) + } + isAny, err := anypb.New(is) + if err != nil { + return nil, fmt.Errorf("AttributeConflict could not marshal is: %w", err) + } + + return &commonpb.AttributeConflict{ + Is: isAny, + Want: wantAny, + }, nil +} diff --git a/services/tasks-svc/internal/ward/ward.go b/services/tasks-svc/internal/ward/ward.go index 2c7ca3326..f39c7c92f 100644 --- a/services/tasks-svc/internal/ward/ward.go +++ b/services/tasks-svc/internal/ward/ward.go @@ -3,14 +3,17 @@ package ward import ( "common" "context" + commonpb "gen/libs/common/v1" pb "gen/services/tasks_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwutil" "tasks-svc/internal/tracking" + "tasks-svc/internal/util" "tasks-svc/repos/ward_repo" ) @@ -140,8 +143,6 @@ func (ServiceServer) GetRecentWards(ctx context.Context, req *pb.GetRecentWardsR } func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) (*pb.UpdateWardResponse, error) { - wardRepo := ward_repo.New(hwdb.GetDB()) - // TODO: Auth id, err := uuid.Parse(req.Id) @@ -149,7 +150,21 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - consistency, err := wardRepo.UpdateWard(ctx, ward_repo.UpdateWardParams{ + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + // Start TX + tx, rollback, err := hwdb.BeginTx(hwdb.GetDB(), ctx) + if err != nil { + return nil, err + } + defer rollback() + wardRepo := ward_repo.New(tx) + + // Do Update + result, err := wardRepo.UpdateWard(ctx, ward_repo.UpdateWardParams{ ID: id, Name: req.Name, }) @@ -158,11 +173,46 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) return nil, err } + // conflict detection + if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + conflicts := make(map[string]*commonpb.AttributeConflict) + + // wards are not event-sourced, we thus don't have information on what has changed since + // however, wards (currently) only have one field: Name, thus it must have been changed + if req.Name != nil { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(result.OldName), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + // prevent the update + if err := hwdb.Error(ctx, tx.Rollback(ctx)); err != nil { + return nil, err + } + + // return conflict + return &pb.UpdateWardResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: common.ConsistencyToken(result.OldConsistency).String(), + }, nil + } + } + + // Commit Update + if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil { + return nil, err + } + tracking.AddWardToRecentActivity(ctx, id.String()) return &pb.UpdateWardResponse{ - Conflict: nil, // TODO - Consistency: common.ConsistencyToken(consistency).String(), + Conflict: nil, + Consistency: common.ConsistencyToken(result.Consistency).String(), }, nil } diff --git a/services/tasks-svc/repos/ward_repo.sql b/services/tasks-svc/repos/ward_repo.sql index 047603f12..2b5da17e0 100644 --- a/services/tasks-svc/repos/ward_repo.sql +++ b/services/tasks-svc/repos/ward_repo.sql @@ -55,11 +55,19 @@ SELECT EXISTS ( ) ward_exists; -- name: UpdateWard :one +WITH old_table AS ( + SELECT name as old_name, consistency as old_consistency + FROM wards + WHERE wards.id = @id +) UPDATE wards SET name = coalesce(sqlc.narg('name'), name), consistency = consistency + 1 -WHERE id = @id -RETURNING consistency; +WHERE wards.id = @id +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_consistency FROM old_table); -- name: DeleteWard :exec diff --git a/services/tasks-svc/repos/ward_repo/ward_repo.sql.go b/services/tasks-svc/repos/ward_repo/ward_repo.sql.go index cf67ca44a..de2967e37 100644 --- a/services/tasks-svc/repos/ward_repo/ward_repo.sql.go +++ b/services/tasks-svc/repos/ward_repo/ward_repo.sql.go @@ -228,11 +228,19 @@ func (q *Queries) GetWardsWithCounts(ctx context.Context, arg GetWardsWithCounts } const updateWard = `-- name: UpdateWard :one +WITH old_table AS ( + SELECT name as old_name, consistency as old_consistency + FROM wards + WHERE wards.id = $2 +) UPDATE wards SET name = coalesce($1, name), consistency = consistency + 1 -WHERE id = $2 -RETURNING consistency +WHERE wards.id = $2 +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_consistency FROM old_table) ` type UpdateWardParams struct { @@ -240,9 +248,15 @@ type UpdateWardParams struct { ID uuid.UUID } -func (q *Queries) UpdateWard(ctx context.Context, arg UpdateWardParams) (int64, error) { +type UpdateWardRow struct { + Consistency int64 + OldName string + OldConsistency int64 +} + +func (q *Queries) UpdateWard(ctx context.Context, arg UpdateWardParams) (UpdateWardRow, error) { row := q.db.QueryRow(ctx, updateWard, arg.Name, arg.ID) - var consistency int64 - err := row.Scan(&consistency) - return consistency, err + var i UpdateWardRow + err := row.Scan(&i.Consistency, &i.OldName, &i.OldConsistency) + return i, err } diff --git a/services/tasks-svc/stories/WardCRUD_test.go b/services/tasks-svc/stories/WardCRUD_test.go index 2d12037dd..b36819e17 100644 --- a/services/tasks-svc/stories/WardCRUD_test.go +++ b/services/tasks-svc/stories/WardCRUD_test.go @@ -5,6 +5,7 @@ import ( pb "gen/services/tasks_svc/v1" "github.com/google/uuid" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" "hwtesting" "hwutil" "strconv" @@ -378,3 +379,47 @@ func TestGetWardDetails(t *testing.T) { assert.Equal(t, expected, actual) } + +func TestUpdateWardConflict(t *testing.T) { + ctx := context.Background() + wardClient := wardServiceClient() + + // prepare + wardId, initialConsistency := prepareWard(t, ctx, "") + + name1 := "This came first" + + // update 1 + update1Res, err := wardClient.UpdateWard(ctx, &pb.UpdateWardRequest{ + Id: wardId, + Name: &name1, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, update1Res.Conflict) + assert.NotEqual(t, initialConsistency, update1Res.Consistency) + + name2 := "This came second" + + // racing update 2 + update2Res, err := wardClient.UpdateWard(ctx, &pb.UpdateWardRequest{ + Id: wardId, + Name: &name2, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Equal(t, update1Res.Consistency, update2Res.Consistency) + assert.NotNil(t, update2Res.Conflict) + + nameRes := update2Res.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, nameRes) + + nameIs := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Is.UnmarshalTo(nameIs)) + assert.Equal(t, name1, nameIs.Value) + + nameWant := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) + assert.Equal(t, name2, nameWant.Value) + +} From 53d3d4d264316e0493e12d93365fa97ae5e6cbb8 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 26 Sep 2024 11:16:52 +0200 Subject: [PATCH 02/42] bed conflicts --- services/tasks-svc/internal/bed/bed.go | 67 ++++++++++++++++- services/tasks-svc/internal/ward/ward.go | 4 +- services/tasks-svc/repos/bed_repo.sql | 16 +++- .../tasks-svc/repos/bed_repo/bed_repo.sql.go | 36 +++++++-- services/tasks-svc/stories/BedCRUD_test.go | 75 +++++++++++++++++++ 5 files changed, 183 insertions(+), 15 deletions(-) diff --git a/services/tasks-svc/internal/bed/bed.go b/services/tasks-svc/internal/bed/bed.go index 746b74e83..ee5cf6cb0 100644 --- a/services/tasks-svc/internal/bed/bed.go +++ b/services/tasks-svc/internal/bed/bed.go @@ -3,11 +3,14 @@ package bed import ( "common" "context" + commonpb "gen/libs/common/v1" "github.com/jackc/pgx/v5/pgconn" "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwlocale" "hwutil" + "tasks-svc/internal/util" "tasks-svc/locale" "tasks-svc/repos/bed_repo" @@ -190,7 +193,6 @@ func (ServiceServer) GetBedsByRoom(ctx context.Context, req *pb.GetBedsByRoomReq } func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (*pb.UpdateBedResponse, error) { - bedRepo := bed_repo.New(hwdb.GetDB()) bedID, err := uuid.Parse(req.Id) if err != nil { @@ -202,7 +204,21 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* return nil, status.Error(codes.InvalidArgument, err.Error()) } - consistency, err := bedRepo.UpdateBed(ctx, bed_repo.UpdateBedParams{ + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + // Start TX + tx, rollback, err := hwdb.BeginTx(hwdb.GetDB(), ctx) + if err != nil { + return nil, err + } + defer rollback() + bedRepo := bed_repo.New(tx) + + // do update + result, err := bedRepo.UpdateBed(ctx, bed_repo.UpdateBedParams{ ID: bedID, Name: req.Name, RoomID: roomId, @@ -212,9 +228,52 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* return nil, err } + // conflict detection + if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + conflicts := make(map[string]*commonpb.AttributeConflict) + + if req.Name != nil && *req.Name != result.OldName { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(result.OldName), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + + if req.RoomId != nil && *req.RoomId != result.OldRoomID.String() { + conflicts["room_id"], err = util.AttributeConflict( + wrapperspb.String(result.OldRoomID.String()), + wrapperspb.String(*req.RoomId), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + // prevent the update + if err := hwdb.Error(ctx, tx.Rollback(ctx)); err != nil { + return nil, err + } + + // return conflict + return &pb.UpdateBedResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: common.ConsistencyToken(result.OldConsistency).String(), + }, nil + } + } + + // commit update + if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil { + return nil, err + } + return &pb.UpdateBedResponse{ - Conflict: nil, // TODO - Consistency: common.ConsistencyToken(consistency).String(), + Conflict: nil, + Consistency: common.ConsistencyToken(result.Consistency).String(), }, nil } diff --git a/services/tasks-svc/internal/ward/ward.go b/services/tasks-svc/internal/ward/ward.go index f39c7c92f..320e42043 100644 --- a/services/tasks-svc/internal/ward/ward.go +++ b/services/tasks-svc/internal/ward/ward.go @@ -177,9 +177,7 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { conflicts := make(map[string]*commonpb.AttributeConflict) - // wards are not event-sourced, we thus don't have information on what has changed since - // however, wards (currently) only have one field: Name, thus it must have been changed - if req.Name != nil { + if req.Name != nil && *req.Name != result.OldName { conflicts["name"], err = util.AttributeConflict( wrapperspb.String(result.OldName), wrapperspb.String(*req.Name), diff --git a/services/tasks-svc/repos/bed_repo.sql b/services/tasks-svc/repos/bed_repo.sql index 2e415abe7..924473c9c 100644 --- a/services/tasks-svc/repos/bed_repo.sql +++ b/services/tasks-svc/repos/bed_repo.sql @@ -27,13 +27,25 @@ WHERE (room_id = sqlc.narg('room_id') OR sqlc.narg('room_id') IS NULL) ORDER BY name ASC; -- name: UpdateBed :one +WITH old_table AS ( + SELECT + name as old_name, + room_id as old_room_id, + consistency as old_consistency + FROM beds + WHERE beds.id = @id +) UPDATE beds SET name = coalesce(sqlc.narg('name'), name), room_id = coalesce(sqlc.narg('room_id'), room_id), consistency = consistency + 1 -WHERE id = @id -RETURNING consistency; +WHERE beds.id = @id +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_room_id FROM old_table), + (SELECT old_consistency FROM old_table); -- name: DeleteBed :exec DELETE FROM beds WHERE id = $1; diff --git a/services/tasks-svc/repos/bed_repo/bed_repo.sql.go b/services/tasks-svc/repos/bed_repo/bed_repo.sql.go index 654424f3a..4b8d6df2f 100644 --- a/services/tasks-svc/repos/bed_repo/bed_repo.sql.go +++ b/services/tasks-svc/repos/bed_repo/bed_repo.sql.go @@ -177,13 +177,25 @@ func (q *Queries) GetBeds(ctx context.Context, roomID uuid.NullUUID) ([]Bed, err } const updateBed = `-- name: UpdateBed :one +WITH old_table AS ( + SELECT + name as old_name, + room_id as old_room_id, + consistency as old_consistency + FROM beds + WHERE beds.id = $3 +) UPDATE beds SET name = coalesce($1, name), room_id = coalesce($2, room_id), consistency = consistency + 1 -WHERE id = $3 -RETURNING consistency +WHERE beds.id = $3 +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_room_id FROM old_table), + (SELECT old_consistency FROM old_table) ` type UpdateBedParams struct { @@ -192,9 +204,21 @@ type UpdateBedParams struct { ID uuid.UUID } -func (q *Queries) UpdateBed(ctx context.Context, arg UpdateBedParams) (int64, error) { +type UpdateBedRow struct { + Consistency int64 + OldName string + OldRoomID uuid.UUID + OldConsistency int64 +} + +func (q *Queries) UpdateBed(ctx context.Context, arg UpdateBedParams) (UpdateBedRow, error) { row := q.db.QueryRow(ctx, updateBed, arg.Name, arg.RoomID, arg.ID) - var consistency int64 - err := row.Scan(&consistency) - return consistency, err + var i UpdateBedRow + err := row.Scan( + &i.Consistency, + &i.OldName, + &i.OldRoomID, + &i.OldConsistency, + ) + return i, err } diff --git a/services/tasks-svc/stories/BedCRUD_test.go b/services/tasks-svc/stories/BedCRUD_test.go index d4a0c0a16..bac53ad19 100644 --- a/services/tasks-svc/stories/BedCRUD_test.go +++ b/services/tasks-svc/stories/BedCRUD_test.go @@ -4,6 +4,7 @@ import ( "context" pb "gen/services/tasks_svc/v1" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" "hwtesting" "hwutil" "strconv" @@ -177,3 +178,77 @@ func TestGetBeds(t *testing.T) { } } + +func TestUpdateBedConflict(t *testing.T) { + ctx := context.Background() + bedClient := bedServiceClient() + + // prepare + wardId, _ := prepareWard(t, ctx, "") + roomId0, _ := prepareRoom(t, ctx, wardId, "0") + + bedId, initialConsistency := prepareBed(t, ctx, roomId0, "") + + name1 := "This came first" + roomId1, _ := prepareRoom(t, ctx, wardId, "1") + + // update 1 + update1Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{ + Id: bedId, + Name: &name1, + RoomId: &roomId1, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, update1Res.Conflict) + assert.NotEqual(t, initialConsistency, update1Res.Consistency) + + name2 := "This came second" + roomId2, _ := prepareRoom(t, ctx, wardId, "2") + + // racing update 2 + update2Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{ + Id: bedId, + Name: &name2, + RoomId: &roomId2, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Equal(t, update1Res.Consistency, update2Res.Consistency) + assert.NotNil(t, update2Res.Conflict) + + nameRes := update2Res.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, nameRes) + + nameIs := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Is.UnmarshalTo(nameIs)) + assert.Equal(t, name1, nameIs.Value) + + nameWant := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) + assert.Equal(t, name2, nameWant.Value) + + roomRes := update2Res.Conflict.ConflictingAttributes["room_id"] + assert.NotNil(t, roomRes) + + roomIs := &wrapperspb.StringValue{} + assert.NoError(t, roomRes.Is.UnmarshalTo(roomIs)) + assert.Equal(t, roomId1, roomIs.Value) + + roomWant := &wrapperspb.StringValue{} + assert.NoError(t, roomRes.Want.UnmarshalTo(roomWant)) + assert.Equal(t, roomId2, roomWant.Value) + + // racing update 3 + update3Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{ + Id: bedId, + Name: &name2, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Equal(t, update1Res.Consistency, update2Res.Consistency) + assert.NotNil(t, update2Res.Conflict) + + assert.NotNil(t, update3Res.Conflict.ConflictingAttributes["name"]) + assert.Nil(t, update3Res.Conflict.ConflictingAttributes["room_id"]) +} From 962c2c8e471e43d437b39d64a993eb1601c9c909 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 26 Sep 2024 12:03:16 +0200 Subject: [PATCH 03/42] tasktemplate --- gen/dart/lib/libs/common/v1/conflict.pb.dart | 17 ++- .../lib/libs/common/v1/conflict.pbjson.dart | 7 +- gen/go/libs/common/v1/conflict.pb.go | 63 +++++---- gen/ts/libs/common/v1/conflict_pb.d.ts | 4 + gen/ts/libs/common/v1/conflict_pb.js | 32 ++++- proto/libs/common/v1/conflict.proto | 4 +- services/tasks-svc/internal/bed/bed.go | 10 +- .../internal/task-template/task_template.go | 120 +++++++++++++++-- .../tasks-svc/repos/task_template_repo.sql | 33 ++++- .../task_template_repo.sql.go | 66 +++++++--- services/tasks-svc/stories/BedCRUD_test.go | 1 + .../stories/TaskTemplateCRUD_test.go | 124 ++++++++++++++++++ services/tasks-svc/stories/WardCRUD_test.go | 1 + 13 files changed, 414 insertions(+), 68 deletions(-) diff --git a/gen/dart/lib/libs/common/v1/conflict.pb.dart b/gen/dart/lib/libs/common/v1/conflict.pb.dart index 42ae5dab2..56faf470d 100644 --- a/gen/dart/lib/libs/common/v1/conflict.pb.dart +++ b/gen/dart/lib/libs/common/v1/conflict.pb.dart @@ -25,11 +25,15 @@ import '../../../google/protobuf/any.pb.dart' as $19; class Conflict extends $pb.GeneratedMessage { factory Conflict({ $core.Map<$core.String, AttributeConflict>? conflictingAttributes, + $core.bool? historyMissing, }) { final $result = create(); if (conflictingAttributes != null) { $result.conflictingAttributes.addAll(conflictingAttributes); } + if (historyMissing != null) { + $result.historyMissing = historyMissing; + } return $result; } Conflict._() : super(); @@ -38,6 +42,7 @@ class Conflict extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Conflict', package: const $pb.PackageName(_omitMessageNames ? '' : 'libs.common.v1'), createEmptyInstance: create) ..m<$core.String, AttributeConflict>(1, _omitFieldNames ? '' : 'conflictingAttributes', entryClassName: 'Conflict.ConflictingAttributesEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: AttributeConflict.create, valueDefaultOrMaker: AttributeConflict.getDefault, packageName: const $pb.PackageName('libs.common.v1')) + ..aOB(2, _omitFieldNames ? '' : 'historyMissing') ..hasRequiredFields = false ; @@ -62,9 +67,19 @@ class Conflict extends $pb.GeneratedMessage { static Conflict getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Conflict? _defaultInstance; - /// might be empty, in that case we don't have the history to calculate the conflicting attributes + /// when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. + /// it is then on you to compare these against your view of the world @$pb.TagNumber(1) $core.Map<$core.String, AttributeConflict> get conflictingAttributes => $_getMap(0); + + @$pb.TagNumber(2) + $core.bool get historyMissing => $_getBF(1); + @$pb.TagNumber(2) + set historyMissing($core.bool v) { $_setBool(1, v); } + @$pb.TagNumber(2) + $core.bool hasHistoryMissing() => $_has(1); + @$pb.TagNumber(2) + void clearHistoryMissing() => clearField(2); } class AttributeConflict extends $pb.GeneratedMessage { diff --git a/gen/dart/lib/libs/common/v1/conflict.pbjson.dart b/gen/dart/lib/libs/common/v1/conflict.pbjson.dart index 8d67aa01a..b3490a427 100644 --- a/gen/dart/lib/libs/common/v1/conflict.pbjson.dart +++ b/gen/dart/lib/libs/common/v1/conflict.pbjson.dart @@ -18,6 +18,7 @@ const Conflict$json = { '1': 'Conflict', '2': [ {'1': 'conflicting_attributes', '3': 1, '4': 3, '5': 11, '6': '.libs.common.v1.Conflict.ConflictingAttributesEntry', '10': 'conflictingAttributes'}, + {'1': 'history_missing', '3': 2, '4': 1, '5': 8, '10': 'historyMissing'}, ], '3': [Conflict_ConflictingAttributesEntry$json], }; @@ -36,9 +37,9 @@ const Conflict_ConflictingAttributesEntry$json = { final $typed_data.Uint8List conflictDescriptor = $convert.base64Decode( 'CghDb25mbGljdBJqChZjb25mbGljdGluZ19hdHRyaWJ1dGVzGAEgAygLMjMubGlicy5jb21tb2' '4udjEuQ29uZmxpY3QuQ29uZmxpY3RpbmdBdHRyaWJ1dGVzRW50cnlSFWNvbmZsaWN0aW5nQXR0' - 'cmlidXRlcxprChpDb25mbGljdGluZ0F0dHJpYnV0ZXNFbnRyeRIQCgNrZXkYASABKAlSA2tleR' - 'I3CgV2YWx1ZRgCIAEoCzIhLmxpYnMuY29tbW9uLnYxLkF0dHJpYnV0ZUNvbmZsaWN0UgV2YWx1' - 'ZToCOAE='); + 'cmlidXRlcxInCg9oaXN0b3J5X21pc3NpbmcYAiABKAhSDmhpc3RvcnlNaXNzaW5nGmsKGkNvbm' + 'ZsaWN0aW5nQXR0cmlidXRlc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EjcKBXZhbHVlGAIgASgL' + 'MiEubGlicy5jb21tb24udjEuQXR0cmlidXRlQ29uZmxpY3RSBXZhbHVlOgI4AQ=='); @$core.Deprecated('Use attributeConflictDescriptor instead') const AttributeConflict$json = { diff --git a/gen/go/libs/common/v1/conflict.pb.go b/gen/go/libs/common/v1/conflict.pb.go index abc222b05..ef423c021 100644 --- a/gen/go/libs/common/v1/conflict.pb.go +++ b/gen/go/libs/common/v1/conflict.pb.go @@ -34,8 +34,10 @@ type Conflict struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // might be empty, in that case we don't have the history to calculate the conflicting attributes + // when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. + // it is then on you to compare these against your view of the world ConflictingAttributes map[string]*AttributeConflict `protobuf:"bytes,1,rep,name=conflicting_attributes,json=conflictingAttributes,proto3" json:"conflicting_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + HistoryMissing bool `protobuf:"varint,2,opt,name=history_missing,json=historyMissing,proto3" json:"history_missing,omitempty"` } func (x *Conflict) Reset() { @@ -77,6 +79,13 @@ func (x *Conflict) GetConflictingAttributes() map[string]*AttributeConflict { return nil } +func (x *Conflict) GetHistoryMissing() bool { + if x != nil { + return x.HistoryMissing + } + return false +} + type AttributeConflict struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -139,7 +148,7 @@ var file_libs_common_v1_conflict_proto_rawDesc = []byte{ 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe3, 0x01, 0x0a, 0x08, 0x43, + 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8c, 0x02, 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x6a, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, @@ -147,30 +156,32 @@ var file_libs_common_v1_conflict_proto_rawDesc = []byte{ 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x1a, 0x6b, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, - 0x6e, 0x67, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x63, 0x0a, 0x11, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x24, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x02, 0x69, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x77, - 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, - 0x04, 0x77, 0x61, 0x6e, 0x74, 0x42, 0x91, 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x69, - 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x43, 0x6f, - 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x12, 0x67, - 0x65, 0x6e, 0x2f, 0x6c, 0x69, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, - 0x31, 0xa2, 0x02, 0x03, 0x4c, 0x43, 0x58, 0xaa, 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x2e, 0x43, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x5c, - 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1a, 0x4c, 0x69, 0x62, 0x73, - 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, 0x4c, 0x69, 0x62, 0x73, 0x3a, 0x3a, 0x43, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x74, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x1a, 0x6b, 0x0a, 0x1a, + 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6c, 0x69, + 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x63, 0x0a, 0x11, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x24, + 0x0a, 0x02, 0x69, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, + 0x52, 0x02, 0x69, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x77, 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x77, 0x61, 0x6e, 0x74, 0x42, 0x91, + 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x12, 0x67, 0x65, 0x6e, 0x2f, 0x6c, 0x69, 0x62, 0x73, + 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x4c, 0x43, 0x58, + 0xaa, 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, + 0x31, 0xca, 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x1a, 0x4c, 0x69, 0x62, 0x73, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x10, 0x4c, 0x69, 0x62, 0x73, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/gen/ts/libs/common/v1/conflict_pb.d.ts b/gen/ts/libs/common/v1/conflict_pb.d.ts index 8c850b6ac..1c556cdd3 100644 --- a/gen/ts/libs/common/v1/conflict_pb.d.ts +++ b/gen/ts/libs/common/v1/conflict_pb.d.ts @@ -7,6 +7,9 @@ export class Conflict extends jspb.Message { getConflictingAttributesMap(): jspb.Map; clearConflictingAttributesMap(): Conflict; + getHistoryMissing(): boolean; + setHistoryMissing(value: boolean): Conflict; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Conflict.AsObject; static toObject(includeInstance: boolean, msg: Conflict): Conflict.AsObject; @@ -18,6 +21,7 @@ export class Conflict extends jspb.Message { export namespace Conflict { export type AsObject = { conflictingAttributesMap: Array<[string, AttributeConflict.AsObject]>, + historyMissing: boolean, } } diff --git a/gen/ts/libs/common/v1/conflict_pb.js b/gen/ts/libs/common/v1/conflict_pb.js index c6aa8144a..be9273bb5 100644 --- a/gen/ts/libs/common/v1/conflict_pb.js +++ b/gen/ts/libs/common/v1/conflict_pb.js @@ -99,7 +99,8 @@ proto.libs.common.v1.Conflict.prototype.toObject = function(opt_includeInstance) */ proto.libs.common.v1.Conflict.toObject = function(includeInstance, msg) { var f, obj = { - conflictingAttributesMap: (f = msg.getConflictingAttributesMap()) ? f.toObject(includeInstance, proto.libs.common.v1.AttributeConflict.toObject) : [] + conflictingAttributesMap: (f = msg.getConflictingAttributesMap()) ? f.toObject(includeInstance, proto.libs.common.v1.AttributeConflict.toObject) : [], + historyMissing: jspb.Message.getBooleanFieldWithDefault(msg, 2, false) }; if (includeInstance) { @@ -142,6 +143,10 @@ proto.libs.common.v1.Conflict.deserializeBinaryFromReader = function(msg, reader jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.libs.common.v1.AttributeConflict.deserializeBinaryFromReader, "", new proto.libs.common.v1.AttributeConflict()); }); break; + case 2: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setHistoryMissing(value); + break; default: reader.skipField(); break; @@ -175,6 +180,13 @@ proto.libs.common.v1.Conflict.serializeBinaryToWriter = function(message, writer if (f && f.getLength() > 0) { f.serializeBinary(1, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.libs.common.v1.AttributeConflict.serializeBinaryToWriter); } + f = message.getHistoryMissing(); + if (f) { + writer.writeBool( + 2, + f + ); + } }; @@ -201,6 +213,24 @@ proto.libs.common.v1.Conflict.prototype.clearConflictingAttributesMap = function }; +/** + * optional bool history_missing = 2; + * @return {boolean} + */ +proto.libs.common.v1.Conflict.prototype.getHistoryMissing = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 2, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.libs.common.v1.Conflict} returns this + */ +proto.libs.common.v1.Conflict.prototype.setHistoryMissing = function(value) { + return jspb.Message.setProto3BooleanField(this, 2, value); +}; + + diff --git a/proto/libs/common/v1/conflict.proto b/proto/libs/common/v1/conflict.proto index 47baed387..323d86fb9 100644 --- a/proto/libs/common/v1/conflict.proto +++ b/proto/libs/common/v1/conflict.proto @@ -14,8 +14,10 @@ option go_package = "gen/libs/common/v1"; // If WAS == IS, or WANT and IS are merge-able (e.g., requested action changes "name", and another action has changed "age"), no conflict arises. // Warning: If a previous action has deleted the resource, an error is raised, and no conflict returned. message Conflict { - // might be empty, in that case we don't have the history to calculate the conflicting attributes + // when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. + // it is then on you to compare these against your view of the world map conflicting_attributes = 1; + bool history_missing = 2; } message AttributeConflict { diff --git a/services/tasks-svc/internal/bed/bed.go b/services/tasks-svc/internal/bed/bed.go index ee5cf6cb0..25e542cf6 100644 --- a/services/tasks-svc/internal/bed/bed.go +++ b/services/tasks-svc/internal/bed/bed.go @@ -230,6 +230,10 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* // conflict detection if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + // bed is not event sourced yet and has more than one field, + // thus we are not able to pinpoint conflicts to a field, we only know *some* update has happened since + // for convenience we are filtering out obvious non-conflicts, where the update is the same as the is state, or the field was not changed + conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { @@ -260,13 +264,15 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* // return conflict return &pb.UpdateBedResponse{ - Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Conflict: &commonpb.Conflict{ + ConflictingAttributes: conflicts, + HistoryMissing: true, + }, Consistency: common.ConsistencyToken(result.OldConsistency).String(), }, nil } } - // commit update if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil { return nil, err } diff --git a/services/tasks-svc/internal/task-template/task_template.go b/services/tasks-svc/internal/task-template/task_template.go index 7a4c528fe..db5b69b82 100644 --- a/services/tasks-svc/internal/task-template/task_template.go +++ b/services/tasks-svc/internal/task-template/task_template.go @@ -3,11 +3,14 @@ package task_template import ( "common" "context" + commonpb "gen/libs/common/v1" "github.com/google/uuid" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwutil" + "tasks-svc/internal/util" "tasks-svc/repos/task_template_repo" pb "gen/services/tasks_svc/v1" @@ -140,7 +143,7 @@ func (ServiceServer) DeleteTaskTemplateSubTask(ctx context.Context, req *pb.Dele } // increase consistency of taskTemplate - consistency, err := templateRepo.UpdateTaskTemplate(ctx, task_template_repo.UpdateTaskTemplateParams{ + ttResult, err := templateRepo.UpdateTaskTemplate(ctx, task_template_repo.UpdateTaskTemplateParams{ ID: subtask.TaskTemplateID, }) if err := hwdb.Error(ctx, err); err != nil { @@ -158,12 +161,11 @@ func (ServiceServer) DeleteTaskTemplateSubTask(ctx context.Context, req *pb.Dele Msg("taskTemplateSubtask deleted") return &pb.DeleteTaskTemplateSubTaskResponse{ - TaskTemplateConsistency: common.ConsistencyToken(consistency).String(), + TaskTemplateConsistency: common.ConsistencyToken(ttResult.Consistency).String(), }, nil } func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskTemplateRequest) (*pb.UpdateTaskTemplateResponse, error) { - templateRepo := task_template_repo.New(hwdb.GetDB()) // TODO: Auth @@ -172,7 +174,21 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT return nil, status.Error(codes.InvalidArgument, err.Error()) } - consistency, err := templateRepo.UpdateTaskTemplate(ctx, task_template_repo.UpdateTaskTemplateParams{ + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + // Start TX + tx, rollback, err := hwdb.BeginTx(hwdb.GetDB(), ctx) + if err != nil { + return nil, err + } + defer rollback() + templateRepo := task_template_repo.New(hwdb.GetDB()) + + // do update + result, err := templateRepo.UpdateTaskTemplate(ctx, task_template_repo.UpdateTaskTemplateParams{ Name: req.Name, Description: req.Description, ID: id, @@ -182,13 +198,65 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT return nil, err } + // conflict detection + if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + // task_template is not event sourced yet and has more than one field, + // thus we are not able to pinpoint conflicts to a field, we only know *some* update has happened since + // for convenience we are filtering out obvious non-conflicts, where the update is the same as the is state, or the field was not changed + + conflicts := make(map[string]*commonpb.AttributeConflict) + + if req.Name != nil && *req.Name != result.OldName { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(result.OldName), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + if req.Description != nil && *req.Description != result.OldDescription { + conflicts["description"], err = util.AttributeConflict( + wrapperspb.String(result.OldDescription), + wrapperspb.String(*req.Description), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + // prevent the update + if err := hwdb.Error(ctx, tx.Rollback(ctx)); err != nil { + return nil, err + } + + // return conflict + return &pb.UpdateTaskTemplateResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: common.ConsistencyToken(result.OldConsistency).String(), + }, nil + } + } + + // Commit Update + if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil { + return nil, err + } + return &pb.UpdateTaskTemplateResponse{ - Conflict: nil, // TODO - Consistency: common.ConsistencyToken(consistency).String(), + Conflict: nil, + Consistency: common.ConsistencyToken(result.Consistency).String(), }, nil } func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.UpdateTaskTemplateSubTaskRequest) (*pb.UpdateTaskTemplateSubTaskResponse, error) { + + expConsistency, ok := hwutil.ParseConsistency(req.TaskTemplateConsistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "task_template_consistency") + } + // TX tx, rollback, err := hwdb.BeginTx(hwdb.GetDB(), ctx) if err != nil { @@ -205,9 +273,9 @@ func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.Upda } // update subtask and get related taskTemplate - taskTemplateID, err := templateRepo.UpdateSubtask(ctx, task_template_repo.UpdateSubtaskParams{ - Name: req.Name, + subTaskResult, err := templateRepo.UpdateSubtask(ctx, task_template_repo.UpdateSubtaskParams{ ID: id, + Name: req.Name, }) err = hwdb.Error(ctx, err) if err != nil { @@ -215,21 +283,49 @@ func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.Upda } // increase consistency of taskTemplate - consistency, err := templateRepo.UpdateTaskTemplate(ctx, task_template_repo.UpdateTaskTemplateParams{ - ID: taskTemplateID, + result, err := templateRepo.UpdateTaskTemplate(ctx, task_template_repo.UpdateTaskTemplateParams{ + ID: subTaskResult.TaskTemplateID, }) if err := hwdb.Error(ctx, err); err != nil { return nil, err } + // conflict detection + if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + conflicts := make(map[string]*commonpb.AttributeConflict) + + if req.Name != nil && *req.Name != subTaskResult.OldName { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(subTaskResult.OldName), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + // prevent the updates + if err := hwdb.Error(ctx, tx.Rollback(ctx)); err != nil { + return nil, err + } + + // return conflict + return &pb.UpdateTaskTemplateSubTaskResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + TaskTemplateConsistency: common.ConsistencyToken(result.OldConsistency).String(), + }, nil + } + } + // commit if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil { return nil, err } return &pb.UpdateTaskTemplateSubTaskResponse{ - Conflict: nil, // TODO - TaskTemplateConsistency: common.ConsistencyToken(consistency).String(), + Conflict: nil, + TaskTemplateConsistency: common.ConsistencyToken(result.Consistency).String(), }, nil } diff --git a/services/tasks-svc/repos/task_template_repo.sql b/services/tasks-svc/repos/task_template_repo.sql index ece93b0f9..48a5f8ade 100644 --- a/services/tasks-svc/repos/task_template_repo.sql +++ b/services/tasks-svc/repos/task_template_repo.sql @@ -34,22 +34,41 @@ AND (task_templates.ward_id IS NULL OR NOT @private_only::bool) AND (task_templates.created_by = sqlc.narg('creator_id') OR sqlc.narg('creator_id') IS NULL); -- name: UpdateTaskTemplate :one +WITH old_table AS ( + SELECT + name as old_name, + description as old_description, + consistency as old_consistency + FROM task_templates + WHERE task_templates.id = @id +) UPDATE task_templates SET name = coalesce(sqlc.narg('name'), name), description = coalesce(sqlc.narg('description'), description), consistency = consistency + 1 -WHERE id = @id -RETURNING consistency; +WHERE task_templates.id = @id +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_description FROM old_table), + (SELECT old_consistency FROM old_table); -- name: UpdateSubtask :one +WITH old_table AS ( + SELECT name as old_name + FROM task_template_subtasks + WHERE task_template_subtasks.id = @id +) UPDATE task_template_subtasks ttst SET name = coalesce(sqlc.narg('name'), name) WHERE ttst.id = @id -RETURNING ( - SELECT tt.id - FROM task_templates tt - WHERE tt.id = ttst.task_template_id -); +RETURNING + ( + SELECT tt.id as task_template_id + FROM task_templates tt + WHERE tt.id = ttst.task_template_id + ), + (SELECT old_name FROM old_table); -- name: DeleteSubtask :one DELETE FROM task_template_subtasks WHERE id = @id RETURNING *; diff --git a/services/tasks-svc/repos/task_template_repo/task_template_repo.sql.go b/services/tasks-svc/repos/task_template_repo/task_template_repo.sql.go index 68cbffc62..fe453eaa0 100644 --- a/services/tasks-svc/repos/task_template_repo/task_template_repo.sql.go +++ b/services/tasks-svc/repos/task_template_repo/task_template_repo.sql.go @@ -154,14 +154,21 @@ func (q *Queries) GetAllTaskTemplatesWithSubTasks(ctx context.Context, arg GetAl } const updateSubtask = `-- name: UpdateSubtask :one +WITH old_table AS ( + SELECT name as old_name + FROM task_template_subtasks + WHERE task_template_subtasks.id = $2 +) UPDATE task_template_subtasks ttst SET name = coalesce($1, name) WHERE ttst.id = $2 -RETURNING ( - SELECT tt.id - FROM task_templates tt - WHERE tt.id = ttst.task_template_id -) +RETURNING + ( + SELECT tt.id as task_template_id + FROM task_templates tt + WHERE tt.id = ttst.task_template_id + ), + (SELECT old_name FROM old_table) ` type UpdateSubtaskParams struct { @@ -169,20 +176,37 @@ type UpdateSubtaskParams struct { ID uuid.UUID } -func (q *Queries) UpdateSubtask(ctx context.Context, arg UpdateSubtaskParams) (uuid.UUID, error) { +type UpdateSubtaskRow struct { + TaskTemplateID uuid.UUID + OldName string +} + +func (q *Queries) UpdateSubtask(ctx context.Context, arg UpdateSubtaskParams) (UpdateSubtaskRow, error) { row := q.db.QueryRow(ctx, updateSubtask, arg.Name, arg.ID) - var id uuid.UUID - err := row.Scan(&id) - return id, err + var i UpdateSubtaskRow + err := row.Scan(&i.TaskTemplateID, &i.OldName) + return i, err } const updateTaskTemplate = `-- name: UpdateTaskTemplate :one +WITH old_table AS ( + SELECT + name as old_name, + description as old_description, + consistency as old_consistency + FROM task_templates + WHERE task_templates.id = $3 +) UPDATE task_templates SET name = coalesce($1, name), description = coalesce($2, description), consistency = consistency + 1 -WHERE id = $3 -RETURNING consistency +WHERE task_templates.id = $3 +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_description FROM old_table), + (SELECT old_consistency FROM old_table) ` type UpdateTaskTemplateParams struct { @@ -191,9 +215,21 @@ type UpdateTaskTemplateParams struct { ID uuid.UUID } -func (q *Queries) UpdateTaskTemplate(ctx context.Context, arg UpdateTaskTemplateParams) (int64, error) { +type UpdateTaskTemplateRow struct { + Consistency int64 + OldName string + OldDescription string + OldConsistency int64 +} + +func (q *Queries) UpdateTaskTemplate(ctx context.Context, arg UpdateTaskTemplateParams) (UpdateTaskTemplateRow, error) { row := q.db.QueryRow(ctx, updateTaskTemplate, arg.Name, arg.Description, arg.ID) - var consistency int64 - err := row.Scan(&consistency) - return consistency, err + var i UpdateTaskTemplateRow + err := row.Scan( + &i.Consistency, + &i.OldName, + &i.OldDescription, + &i.OldConsistency, + ) + return i, err } diff --git a/services/tasks-svc/stories/BedCRUD_test.go b/services/tasks-svc/stories/BedCRUD_test.go index bac53ad19..ac22ea6b1 100644 --- a/services/tasks-svc/stories/BedCRUD_test.go +++ b/services/tasks-svc/stories/BedCRUD_test.go @@ -61,6 +61,7 @@ func TestCreateUpdateGetBed(t *testing.T) { } updateRes, err := bedClient.UpdateBed(ctx, updateReq) assert.NoError(t, err, "could not update bed after creation") + assert.Nil(t, updateRes.Conflict) assert.NotEqual(t, getBedRes.Consistency, updateRes.Consistency, "consistency has not changed in update") diff --git a/services/tasks-svc/stories/TaskTemplateCRUD_test.go b/services/tasks-svc/stories/TaskTemplateCRUD_test.go index 093e2b18c..153466c29 100644 --- a/services/tasks-svc/stories/TaskTemplateCRUD_test.go +++ b/services/tasks-svc/stories/TaskTemplateCRUD_test.go @@ -4,6 +4,7 @@ import ( "context" pb "gen/services/tasks_svc/v1" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" "hwtesting" "hwutil" "testing" @@ -127,3 +128,126 @@ func TestCreateUpdateGetTaskTemplate(t *testing.T) { assert.Equal(t, updateStRes.TaskTemplateConsistency, template.Consistency) } + +func TestUpdateTaskTemplateConflict(t *testing.T) { + ctx := context.Background() + taskTemplateClient := taskTemplateServiceClient() + + ttRes, err := taskTemplateClient.CreateTaskTemplate(ctx, &pb.CreateTaskTemplateRequest{ + Name: t.Name(), + Description: nil, + WardId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + ttId := ttRes.Id + initialConsistency := ttRes.Consistency + + name1 := "This came first" + + // update 1 + update1Res, err := taskTemplateClient.UpdateTaskTemplate(ctx, &pb.UpdateTaskTemplateRequest{ + Id: ttId, + Name: &name1, + Description: &name1, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, update1Res.Conflict) + assert.NotEqual(t, initialConsistency, update1Res.Consistency) + + name2 := "This came second" + + // racing update 2 + update2Res, err := taskTemplateClient.UpdateTaskTemplate(ctx, &pb.UpdateTaskTemplateRequest{ + Id: ttId, + Name: &name2, + Description: &name2, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Equal(t, update1Res.Consistency, update2Res.Consistency) + assert.NotNil(t, update2Res.Conflict) + + nameRes := update2Res.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, nameRes) + + nameIs := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Is.UnmarshalTo(nameIs)) + assert.Equal(t, name1, nameIs.Value) + + nameWant := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) + assert.Equal(t, name2, nameWant.Value) + + descrRes := update2Res.Conflict.ConflictingAttributes["description"] + assert.NotNil(t, descrRes) + + descrIs := &wrapperspb.StringValue{} + assert.NoError(t, descrRes.Is.UnmarshalTo(descrIs)) + assert.Equal(t, name1, descrIs.Value) + + descrWant := &wrapperspb.StringValue{} + assert.NoError(t, descrRes.Want.UnmarshalTo(descrWant)) + assert.Equal(t, name2, descrWant.Value) + +} + +func TestUpdateTaskTemplateSubTaskConflict(t *testing.T) { + ctx := context.Background() + taskTemplateClient := taskTemplateServiceClient() + + ttRes, err := taskTemplateClient.CreateTaskTemplate(ctx, &pb.CreateTaskTemplateRequest{ + Name: t.Name(), + Description: nil, + WardId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + stRes, err := taskTemplateClient.CreateTaskTemplateSubTask(ctx, &pb.CreateTaskTemplateSubTaskRequest{ + TaskTemplateId: ttRes.Id, + Name: t.Name(), + }) + assert.NoError(t, err) + + stId := stRes.Id + initialConsistency := stRes.TaskTemplateConsistency + + name1 := "This came first" + + // update 1 + update1Res, err := taskTemplateClient.UpdateTaskTemplateSubTask(ctx, &pb.UpdateTaskTemplateSubTaskRequest{ + SubtaskId: stId, + Name: &name1, + TaskTemplateConsistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, update1Res.Conflict) + assert.NotEqual(t, initialConsistency, update1Res.TaskTemplateConsistency) + + name2 := "This came second" + + // racing update 2 + update2Res, err := taskTemplateClient.UpdateTaskTemplateSubTask(ctx, &pb.UpdateTaskTemplateSubTaskRequest{ + SubtaskId: stId, + Name: &name2, + TaskTemplateConsistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Equal(t, update1Res.TaskTemplateConsistency, update2Res.TaskTemplateConsistency) + assert.NotNil(t, update2Res.Conflict) + + nameRes := update2Res.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, nameRes) + + nameIs := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Is.UnmarshalTo(nameIs)) + assert.Equal(t, name1, nameIs.Value) + + nameWant := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) + assert.Equal(t, name2, nameWant.Value) + +} diff --git a/services/tasks-svc/stories/WardCRUD_test.go b/services/tasks-svc/stories/WardCRUD_test.go index b36819e17..4ffa84a95 100644 --- a/services/tasks-svc/stories/WardCRUD_test.go +++ b/services/tasks-svc/stories/WardCRUD_test.go @@ -53,6 +53,7 @@ func TestCreateUpdateGetWard(t *testing.T) { } updateRes, err := wardClient.UpdateWard(ctx, updateReq) assert.NoError(t, err, "could not update ward after creation") + assert.Nil(t, updateRes.Conflict) assert.NotEqual(t, getWardRes.Consistency, updateRes.Consistency, "consistency has not changed in update") From 5c990db81932a76601ae0f18edc7ae46cb74cdfd Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 26 Sep 2024 12:14:06 +0200 Subject: [PATCH 04/42] room --- services/tasks-svc/internal/room/room.go | 56 +++++++++++++++++-- services/tasks-svc/repos/room_repo.sql | 14 ++++- .../repos/room_repo/room_repo.sql.go | 28 ++++++++-- services/tasks-svc/stories/RoomCRUD_test.go | 46 +++++++++++++++ services/tasks-svc/stories/WardCRUD_test.go | 2 + 5 files changed, 134 insertions(+), 12 deletions(-) diff --git a/services/tasks-svc/internal/room/room.go b/services/tasks-svc/internal/room/room.go index 94a4e185c..dd64b7d54 100644 --- a/services/tasks-svc/internal/room/room.go +++ b/services/tasks-svc/internal/room/room.go @@ -3,12 +3,15 @@ package room import ( "common" "context" + commonpb "gen/libs/common/v1" "github.com/google/uuid" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwutil" "tasks-svc/internal/tracking" + "tasks-svc/internal/util" "tasks-svc/repos/room_repo" pb "gen/services/tasks_svc/v1" @@ -100,7 +103,6 @@ func (ServiceServer) GetRoom(ctx context.Context, req *pb.GetRoomRequest) (*pb.G } func (ServiceServer) UpdateRoom(ctx context.Context, req *pb.UpdateRoomRequest) (*pb.UpdateRoomResponse, error) { - roomRepo := room_repo.New(hwdb.GetDB()) // TODO: Auth @@ -109,7 +111,20 @@ func (ServiceServer) UpdateRoom(ctx context.Context, req *pb.UpdateRoomRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - consistency, err := roomRepo.UpdateRoom(ctx, room_repo.UpdateRoomParams{ + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + // Start TX + tx, rollback, err := hwdb.BeginTx(hwdb.GetDB(), ctx) + if err != nil { + return nil, err + } + defer rollback() + roomRepo := room_repo.New(tx) + + result, err := roomRepo.UpdateRoom(ctx, room_repo.UpdateRoomParams{ ID: patientID, Name: req.Name, }) @@ -118,9 +133,42 @@ func (ServiceServer) UpdateRoom(ctx context.Context, req *pb.UpdateRoomRequest) return nil, err } + // conflict detection + if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + conflicts := make(map[string]*commonpb.AttributeConflict) + + if req.Name != nil && *req.Name != result.OldName { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(result.OldName), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + // prevent the update + if err := hwdb.Error(ctx, tx.Rollback(ctx)); err != nil { + return nil, err + } + + // return conflict + return &pb.UpdateRoomResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: common.ConsistencyToken(result.OldConsistency).String(), + }, nil + } + } + + // Commit Update + if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil { + return nil, err + } + return &pb.UpdateRoomResponse{ - Conflict: nil, // TODO - Consistency: common.ConsistencyToken(consistency).String(), + Conflict: nil, + Consistency: common.ConsistencyToken(result.Consistency).String(), }, nil } diff --git a/services/tasks-svc/repos/room_repo.sql b/services/tasks-svc/repos/room_repo.sql index aef4e4476..6a1275931 100644 --- a/services/tasks-svc/repos/room_repo.sql +++ b/services/tasks-svc/repos/room_repo.sql @@ -76,11 +76,21 @@ WHERE rooms.ward_id = @ward_id ORDER BY rooms.id ASC, beds.name ASC; -- name: UpdateRoom :one +WITH old_table AS ( + SELECT + name as old_name, + consistency as old_consistency + FROM rooms + WHERE rooms.id = @id +) UPDATE rooms SET name = coalesce(sqlc.narg('name'), name), consistency = consistency + 1 -WHERE id = @id -RETURNING consistency; +WHERE rooms.id = @id +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_consistency FROM old_table); -- name: DeleteRoom :exec DELETE FROM rooms WHERE id = @id; diff --git a/services/tasks-svc/repos/room_repo/room_repo.sql.go b/services/tasks-svc/repos/room_repo/room_repo.sql.go index 0e0a94b05..9c1bcd9a2 100644 --- a/services/tasks-svc/repos/room_repo/room_repo.sql.go +++ b/services/tasks-svc/repos/room_repo/room_repo.sql.go @@ -292,11 +292,21 @@ func (q *Queries) GetRoomsWithBedsWithPatientsByWard(ctx context.Context, wardID } const updateRoom = `-- name: UpdateRoom :one +WITH old_table AS ( + SELECT + name as old_name, + consistency as old_consistency + FROM rooms + WHERE rooms.id = $2 +) UPDATE rooms SET name = coalesce($1, name), consistency = consistency + 1 -WHERE id = $2 -RETURNING consistency +WHERE rooms.id = $2 +RETURNING + consistency, + (SELECT old_name FROM old_table), + (SELECT old_consistency FROM old_table) ` type UpdateRoomParams struct { @@ -304,9 +314,15 @@ type UpdateRoomParams struct { ID uuid.UUID } -func (q *Queries) UpdateRoom(ctx context.Context, arg UpdateRoomParams) (int64, error) { +type UpdateRoomRow struct { + Consistency int64 + OldName string + OldConsistency int64 +} + +func (q *Queries) UpdateRoom(ctx context.Context, arg UpdateRoomParams) (UpdateRoomRow, error) { row := q.db.QueryRow(ctx, updateRoom, arg.Name, arg.ID) - var consistency int64 - err := row.Scan(&consistency) - return consistency, err + var i UpdateRoomRow + err := row.Scan(&i.Consistency, &i.OldName, &i.OldConsistency) + return i, err } diff --git a/services/tasks-svc/stories/RoomCRUD_test.go b/services/tasks-svc/stories/RoomCRUD_test.go index 8114d75df..9f9a7a658 100644 --- a/services/tasks-svc/stories/RoomCRUD_test.go +++ b/services/tasks-svc/stories/RoomCRUD_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" pb "gen/services/tasks_svc/v1" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/wrapperspb" "hwtesting" "hwutil" "strconv" @@ -287,3 +288,48 @@ func TestGetRoomOverviewsByWard(t *testing.T) { assert.JSONEq(t, string(expectedRoomAJson), string(resRoomAJson)) assert.JSONEq(t, string(expectedRoomBJson), string(resRoomBJson)) } + +func TestUpdateRoomConflict(t *testing.T) { + ctx := context.Background() + roomClient := roomServiceClient() + + // prepare + wardId, _ := prepareWard(t, ctx, "") + roomId, initialConsistency := prepareRoom(t, ctx, wardId, "") + + name1 := "This came first" + + // update 1 + update1Res, err := roomClient.UpdateRoom(ctx, &pb.UpdateRoomRequest{ + Id: roomId, + Name: &name1, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, update1Res.Conflict) + assert.NotEqual(t, initialConsistency, update1Res.Consistency) + + name2 := "This came second" + + // racing update 2 + update2Res, err := roomClient.UpdateRoom(ctx, &pb.UpdateRoomRequest{ + Id: roomId, + Name: &name2, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Equal(t, update1Res.Consistency, update2Res.Consistency) + assert.NotNil(t, update2Res.Conflict) + + nameRes := update2Res.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, nameRes) + + nameIs := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Is.UnmarshalTo(nameIs)) + assert.Equal(t, name1, nameIs.Value) + + nameWant := &wrapperspb.StringValue{} + assert.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) + assert.Equal(t, name2, nameWant.Value) + +} diff --git a/services/tasks-svc/stories/WardCRUD_test.go b/services/tasks-svc/stories/WardCRUD_test.go index 4ffa84a95..5ee2ddd7d 100644 --- a/services/tasks-svc/stories/WardCRUD_test.go +++ b/services/tasks-svc/stories/WardCRUD_test.go @@ -110,6 +110,8 @@ func TestGetRecentWards(t *testing.T) { Id: patientID, BedId: bedId, }) + time.Sleep(time.Millisecond * 100) + assert.NoError(t, err, "could not assign bed to patient") _, err = taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ Name: t.Name() + " Patient " + bedSuffix + " Task ", From 2d0d6b7adea17d3f29d966d0ab31a18cab373b35 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 26 Sep 2024 14:45:13 +0200 Subject: [PATCH 05/42] qu --- .../tasks-svc/internal/task-template/task_template.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/tasks-svc/internal/task-template/task_template.go b/services/tasks-svc/internal/task-template/task_template.go index db5b69b82..41581c12c 100644 --- a/services/tasks-svc/internal/task-template/task_template.go +++ b/services/tasks-svc/internal/task-template/task_template.go @@ -233,7 +233,10 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT // return conflict return &pb.UpdateTaskTemplateResponse{ - Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Conflict: &commonpb.Conflict{ + ConflictingAttributes: conflicts, + HistoryMissing: true, + }, Consistency: common.ConsistencyToken(result.OldConsistency).String(), }, nil } @@ -312,7 +315,10 @@ func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.Upda // return conflict return &pb.UpdateTaskTemplateSubTaskResponse{ - Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Conflict: &commonpb.Conflict{ + ConflictingAttributes: conflicts, + HistoryMissing: true, + }, TaskTemplateConsistency: common.ConsistencyToken(result.OldConsistency).String(), }, nil } From dc824e8f673988e98962429b87466ffb04013e88 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 27 Sep 2024 08:56:09 +0200 Subject: [PATCH 06/42] update patient --- libs/hwes/aggregate_store.go | 3 + libs/hwes/eventstoredb/aggregate_store.go | 39 ++++- libs/hwes/eventstoredb/integration_test.go | 89 +++++++++++ libs/hwes/go.mod | 44 ++++++ libs/hwes/go.sum | 142 +++++++++++++++--- libs/hwes/integration_test.go | 10 +- libs/hwes/test/memory_aggregate_store.go | 22 ++- .../internal/patient/aggregate/aggregate.go | 22 +++ .../tasks-svc/internal/patient/api/grpc.go | 47 +++++- .../patient/commands/v1/update_patient.go | 31 +++- .../tasks-svc/stories/PatientCRUD_test.go | 59 ++++++++ 11 files changed, 462 insertions(+), 46 deletions(-) create mode 100644 libs/hwes/eventstoredb/integration_test.go diff --git a/libs/hwes/aggregate_store.go b/libs/hwes/aggregate_store.go index be3d41545..cd9a97a4c 100644 --- a/libs/hwes/aggregate_store.go +++ b/libs/hwes/aggregate_store.go @@ -10,6 +10,9 @@ type AggregateStore interface { // Load populates the aggregate to the recent version of the persisted events Load(ctx context.Context, aggregate Aggregate) error + // LoadN populates the aggregate by N-many persisted events + LoadN(ctx context.Context, aggregate Aggregate, N uint64) error + // Save persists all uncommitted events of the aggregate, returns consistency token Save(ctx context.Context, aggregate Aggregate) (common.ConsistencyToken, error) diff --git a/libs/hwes/eventstoredb/aggregate_store.go b/libs/hwes/eventstoredb/aggregate_store.go index 28e34ff77..639dc075a 100644 --- a/libs/hwes/eventstoredb/aggregate_store.go +++ b/libs/hwes/eventstoredb/aggregate_store.go @@ -90,39 +90,62 @@ func (a *AggregateStore) doSave(ctx context.Context, aggregate hwes.Aggregate, g // Implements AggregateStore interface -func (a *AggregateStore) Load(ctx context.Context, aggregate hwes.Aggregate) error { - stream, err := a.es.ReadStream(ctx, aggregate.GetTypeID(), esdb.ReadStreamOptions{}, math.MaxUint64) // MaxUint64 for "all" events +func (a *AggregateStore) LoadN(ctx context.Context, aggregate hwes.Aggregate, N uint64) error { + if N == 0 { + return nil + } + + // start from the beginning, unless we have already processed events + var from esdb.StreamPosition = esdb.Start{} + aE := aggregate.GetAppliedEvents() + if len(aE) != 0 { + // We cant just use the aggreagate's Version here, as we are unable to + // discriminate aggregates where the first event was processed already (version = 0) + // from those aggregates where no event was processed yet (version = 0 by default) + from = esdb.Revision(aE[len(aE)-1].Version + 1) + } + + // open a read stream + stream, err := a.es.ReadStream(ctx, aggregate.GetTypeID(), esdb.ReadStreamOptions{ + Direction: esdb.Forwards, + From: from, + }, N) if err != nil { - return fmt.Errorf("AggregateStore.Load: could not open stream: %w", err) + return fmt.Errorf("AggregateStore.LoadN: could not open stream: %w", err) } defer stream.Close() + // stream events in for { esdbEvent, err := stream.Recv() if errors.Is(err, io.EOF) { - // exit condition for for-loop + // all events were handled! break } else if err != nil { - return fmt.Errorf("AggregateStore.Load: could not read from stream: %w", err) + return fmt.Errorf("AggregateStore.LoadN: could not read from stream: %w", err) } event, err := hwes.NewEventFromRecordedEvent(esdbEvent.Event) if err != nil { - return fmt.Errorf("AggregateStore.Load: %w", err) + return fmt.Errorf("AggregateStore.LoadN: %w", err) } if err := aggregate.Progress(event); err != nil { - return fmt.Errorf("AggregateStore.Load: Progress failed: %w", err) + return fmt.Errorf("AggregateStore.LoadN: Progress failed: %w", err) } } if aggregate.IsDeleted() { - return fmt.Errorf("AggregateStore.Load: aggregate has been marked as deleted") + return fmt.Errorf("AggregateStore.LoadN: aggregate has been marked as deleted") } return nil } +func (a *AggregateStore) Load(ctx context.Context, aggregate hwes.Aggregate) error { + return a.LoadN(ctx, aggregate, math.MaxUint64) // MaxUint64 -> all events +} + func (a *AggregateStore) Save(ctx context.Context, aggregate hwes.Aggregate) (common.ConsistencyToken, error) { // We can switch out the getExpectedRevision strategy for testing optimistic concurrency. // It is not intended to switch the strategy in production. diff --git a/libs/hwes/eventstoredb/integration_test.go b/libs/hwes/eventstoredb/integration_test.go new file mode 100644 index 000000000..7a3c79ef1 --- /dev/null +++ b/libs/hwes/eventstoredb/integration_test.go @@ -0,0 +1,89 @@ +package eventstoredb + +import ( + "context" + "fmt" + "github.com/google/uuid" + zlog "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" + "hwes" + "hwtesting" + "os" + "testing" + "time" +) + +var endpoint string + +func TestMain(m *testing.M) { + ctx, cancel := context.WithCancel(context.Background()) + + zlog.Info().Msg("starting containers") + endpoints, teardownContainers := hwtesting.StartContainers(ctx, hwtesting.Eventstore) + + endpoint = fmt.Sprintf("esdb://%s:%s@%s?tls=false", + hwtesting.EsUser, hwtesting.EsPassword, endpoints.Get(hwtesting.Eventstore)) + + exit := m.Run() + + teardownContainers() + cancel() + os.Exit(exit) +} + +func TestLoadN(t *testing.T) { + zlog.Info().Str("endpoint", endpoint).Msg(t.Name()) + client := SetupEventStore(endpoint) + as := NewAggregateStore(client) + ctx := context.Background() + + id := uuid.New() + eventType := t.Name() + + sendAggregate := hwes.NewAggregateBase(hwes.AggregateType(eventType), id) + sendAggregate.RegisterEventListener(eventType, func(evt hwes.Event) error { return nil }) + + for i := 0; i < 4; i++ { + event, _ := hwes.NewEvent(sendAggregate, eventType, hwes.WithContext(ctx)) + assert.NoError(t, sendAggregate.Apply(event)) + } + + _, err := as.Save(ctx, sendAggregate) + assert.NoError(t, err) + + time.Sleep(time.Millisecond * 100) + + rxAggregate := hwes.NewAggregateBase(hwes.AggregateType(eventType), id) + rxAggregate.RegisterEventListener(eventType, func(evt hwes.Event) error { return nil }) + assert.Len(t, sendAggregate.GetAppliedEvents(), 0) + + // LoadN 0 does nothing + err = as.LoadN(ctx, rxAggregate, 0) + assert.NoError(t, err) + assert.Len(t, rxAggregate.GetAppliedEvents(), 0) + + // LoadN 1 loads the first event + err = as.LoadN(ctx, rxAggregate, 1) + assert.NoError(t, err) + assert.Len(t, rxAggregate.GetAppliedEvents(), 1) + assert.Equal(t, rxAggregate.GetAppliedEvents()[0].Version, uint64(0)) + + // LoadN 1 loads another event + err = as.LoadN(ctx, rxAggregate, 1) + assert.NoError(t, err) + assert.Len(t, rxAggregate.GetAppliedEvents(), 2) + assert.Equal(t, rxAggregate.GetAppliedEvents()[1].Version, uint64(1)) + + // LoadN 2 loads the rest + err = as.LoadN(ctx, rxAggregate, 2) + assert.NoError(t, err) + assert.Len(t, rxAggregate.GetAppliedEvents(), 4) + assert.Equal(t, rxAggregate.GetAppliedEvents()[2].Version, uint64(2)) + assert.Equal(t, rxAggregate.GetAppliedEvents()[3].Version, uint64(3)) + + // LoadN 1 now does nothing anymore + err = as.LoadN(ctx, rxAggregate, 1) + assert.NoError(t, err) + assert.Len(t, rxAggregate.GetAppliedEvents(), 4) + +} diff --git a/libs/hwes/go.mod b/libs/hwes/go.mod index 3f5d43d59..ebdd7e1a9 100644 --- a/libs/hwes/go.mod +++ b/libs/hwes/go.mod @@ -5,6 +5,7 @@ go 1.23 replace ( common => ../common hwlocale => ../hwlocale + hwtesting => ../hwtesting hwutil => ../hwutil telemetry => ../telemetry ) @@ -15,45 +16,87 @@ require ( github.com/google/uuid v1.6.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 + hwtesting v0.0.0 hwutil v0.0.0 telemetry v0.0.0 ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.4.0 // indirect + github.com/Mariscal6/testcontainers-spicedb-go v0.1.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-oidc v2.2.1+incompatible // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/dapr/dapr v1.14.2 // indirect github.com/dapr/go-sdk v1.11.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.2.0+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/fatih/structs v1.1.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-migrate/migrate/v4 v4.18.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/prometheus/client_golang v1.20.3 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/testcontainers/testcontainers-go v0.33.0 // indirect + github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 // indirect + github.com/testcontainers/testcontainers-go/modules/redis v0.33.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.30.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect @@ -63,6 +106,7 @@ require ( go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/atomic v1.10.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect diff --git a/libs/hwes/go.sum b/libs/hwes/go.sum index b212bfd44..4f7e1aa04 100644 --- a/libs/hwes/go.sum +++ b/libs/hwes/go.sum @@ -4,46 +4,63 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/EventStore/EventStore-Client-Go/v4 v4.1.0 h1:P1dYxz+ReXJd7MAPSgZ5MvZltWoJWN3L/2p99yNKkic= github.com/EventStore/EventStore-Client-Go/v4 v4.1.0/go.mod h1:w2z38mcQWUCrN8PzQoS5ZkiC1a2MN6EuSE5CFVBOmfQ= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Mariscal6/testcontainers-spicedb-go v0.1.0 h1:3R0ADWngw92Ri83esfukM38eK5E4D80058VHwftY1Ds= +github.com/Mariscal6/testcontainers-spicedb-go v0.1.0/go.mod h1:ix0Mqepw7XaLDLTlYlIE2inKxZIykSe0I2J2/ZN9o4Q= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/authzed/authzed-go v0.14.0 h1:Lvy0qudgdunuQmzsrO9ljNeCr7Cdfh2x1RwgOPi+M9w= +github.com/authzed/authzed-go v0.14.0/go.mod h1:ZyMR4heb6r5t3LJSu84AoxFXQUtaE+nYBbIvBx6vz5s= +github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b h1:wbh8IK+aMLTCey9sZasO7b6BWLAJnHHvb79fvWCXwxw= +github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= +github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= -github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/dapr/dapr v1.14.2 h1:FCeBtDz7k/ZbmWbd/SfFqrnmzHHC/hmUkGdf9z0fimM= github.com/dapr/dapr v1.14.2/go.mod h1:55ilcTqOGrWoBPYn/Cdj+8RSEWrnvfpwTF5V9mBsSDo= github.com/dapr/go-sdk v1.11.0 h1:clANpOQd6MsfvSa6snaX8MVk6eRx26Vsj5GxGdQ6mpE= github.com/dapr/go-sdk v1.11.0/go.mod h1:btZ/tX8eYnx0fg3HiJUku8J5QBRXHsp3kAB1BUiTxXY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0= +github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -65,25 +82,50 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e h1:XmA6L9IPRdUr28a+SK/oMchGgQy159wvzXA5tJ7l+40= github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e/go.mod h1:AFIo+02s+12CEg8Gzz9kzhCbmbq6JcKNrhHffCGA9z4= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas= +github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -94,6 +136,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -104,12 +148,16 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -126,6 +174,8 @@ github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7s github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -146,28 +196,44 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= +github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E= -github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0= +github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= +github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= +github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 h1:c+Gt+XLJjqFAejgX4hSpnHIpC9eAhvgI/TFWL/PbrFI= +github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0/go.mod h1:I4DazHBoWDyf69ByOIyt3OdNjefiUx372459txOpQ3o= +github.com/testcontainers/testcontainers-go/modules/redis v0.33.0 h1:S/QvMOwpr00MM2aWH+krzP73Erlp/Ug0dr2rkgZYI5s= +github.com/testcontainers/testcontainers-go/modules/redis v0.33.0/go.mod h1:gudb3+6uZ9SsAysOVoLs7nazbjGlkHegBW8nqPXvDMI= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= @@ -186,29 +252,63 @@ go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8d go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= @@ -227,3 +327,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/libs/hwes/integration_test.go b/libs/hwes/integration_test.go index bd3f2634f..a447fae29 100644 --- a/libs/hwes/integration_test.go +++ b/libs/hwes/integration_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "github.com/google/uuid" + "github.com/stretchr/testify/assert" "hwes" "hwes/test" "testing" @@ -141,13 +142,8 @@ func TestIntegration(t *testing.T) { t.Fatal(err) } - if loadedUserAggregate.User.ID != id { - t.Fatal("invalid id") - } - - if loadedUserAggregate.User.Username != "testine.test" { - t.Fatal("invalid username") - } + assert.Equal(t, id, loadedUserAggregate.User.ID) + assert.Equal(t, "testine.test", loadedUserAggregate.User.Username) } func TestAggregateBase_RegisterEventListener_HandleEvent(t *testing.T) { diff --git a/libs/hwes/test/memory_aggregate_store.go b/libs/hwes/test/memory_aggregate_store.go index a85f57405..80bfb1c10 100644 --- a/libs/hwes/test/memory_aggregate_store.go +++ b/libs/hwes/test/memory_aggregate_store.go @@ -5,6 +5,7 @@ import ( "context" "github.com/stretchr/testify/assert" "hwes" + "math" "testing" ) @@ -18,10 +19,23 @@ func NewAggregateStore() *AggregateStore { return &AggregateStore{streams: make(map[string][]hwes.Event)} } -func (a *AggregateStore) Load(ctx context.Context, aggregate hwes.Aggregate) error { +func (a *AggregateStore) LoadN(_ context.Context, aggregate hwes.Aggregate, N uint64) error { events := a.streams[aggregate.GetTypeID()] - for _, event := range events { + skipUpTo := uint64(0) + aE := aggregate.GetAppliedEvents() + if len(aE) != 0 { + skipUpTo = aE[len(aE)-1].Version + 1 + } + + for i, event := range events { + if uint64(i) < skipUpTo { + continue + } + + if uint64(i) >= N { + break + } if err := aggregate.Progress(event); err != nil { return err } @@ -30,6 +44,10 @@ func (a *AggregateStore) Load(ctx context.Context, aggregate hwes.Aggregate) err return nil } +func (a *AggregateStore) Load(ctx context.Context, aggregate hwes.Aggregate) error { + return a.LoadN(ctx, aggregate, math.MaxUint64) +} + func (a *AggregateStore) Save(ctx context.Context, aggregate hwes.Aggregate) (common.ConsistencyToken, error) { uncomittedEventsLen := len(aggregate.GetUncommittedEvents()) if uncomittedEventsLen == 0 { diff --git a/services/tasks-svc/internal/patient/aggregate/aggregate.go b/services/tasks-svc/internal/patient/aggregate/aggregate.go index 2a07c06c6..374a814b1 100644 --- a/services/tasks-svc/internal/patient/aggregate/aggregate.go +++ b/services/tasks-svc/internal/patient/aggregate/aggregate.go @@ -31,6 +31,28 @@ func LoadPatientAggregate(ctx context.Context, as hwes.AggregateStore, id uuid.U return patientAggregate, nil } +func LoadPatientAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *uint64) (*PatientAggregate, *models.Patient, error) { + patientAggregate := NewPatientAggregate(id) + var snapshot *models.Patient + + if pauseAt != nil { + // load pauseAt+1-many events (version is 0-indexed) + if err := as.LoadN(ctx, patientAggregate, *pauseAt+1); err != nil { + return nil, nil, err + } + + patientCopy := *patientAggregate.Patient // deref copies model + snapshot = &patientCopy + } + + // continue loading all other events + if err := as.Load(ctx, patientAggregate); err != nil { + return nil, nil, err + } + + return patientAggregate, snapshot, nil +} + func (a *PatientAggregate) initEventListeners() { a.RegisterEventListener(patientEventsV1.PatientCreated, a.onPatientCreated) a.RegisterEventListener(patientEventsV1.BedAssigned, a.onBedAssigned) diff --git a/services/tasks-svc/internal/patient/api/grpc.go b/services/tasks-svc/internal/patient/api/grpc.go index aea642388..44c86ca95 100644 --- a/services/tasks-svc/internal/patient/api/grpc.go +++ b/services/tasks-svc/internal/patient/api/grpc.go @@ -4,11 +4,13 @@ import ( "common" "context" "fmt" + commonpb "gen/libs/common/v1" pb "gen/services/tasks_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwdb/locale" "hwes" @@ -16,6 +18,7 @@ import ( "tasks-svc/internal/patient/handlers" "tasks-svc/internal/patient/models" "tasks-svc/internal/tracking" + "tasks-svc/internal/util" "tasks-svc/repos/bed_repo" ) @@ -385,15 +388,55 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa return nil, err } - consistency, err := s.handlers.Commands.V1.UpdatePatient(ctx, patientID, req.HumanReadableIdentifier, req.Notes) + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + consistency, conflict, err := s.handlers.Commands.V1.UpdatePatient(ctx, patientID, expConsistency, req.HumanReadableIdentifier, req.Notes) if err != nil { return nil, err } + if conflict != nil { + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + hriUpdateRequested := req.HumanReadableIdentifier != nil && *req.HumanReadableIdentifier != conflict.Is.HumanReadableIdentifier + hriAlreadyUpdated := conflict.Was.HumanReadableIdentifier != conflict.Is.HumanReadableIdentifier + if hriUpdateRequested && hriAlreadyUpdated { + conflicts["human_readable_identifier"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.HumanReadableIdentifier), + wrapperspb.String(*req.HumanReadableIdentifier), + ) + if err != nil { + return nil, err + } + } + + notesUpdateRequested := req.Notes != nil && *req.Notes != conflict.Is.Notes + notesAlreadyUpdated := conflict.Was.Notes != conflict.Is.Notes + if notesUpdateRequested && notesAlreadyUpdated { + conflicts["notes"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.Notes), + wrapperspb.String(*req.Notes), + ) + if err != nil { + return nil, fmt.Errorf("could not marshall notes conflict: %w", err) + } + } + + if len(conflicts) != 0 { + return &pb.UpdatePatientResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: common.ConsistencyToken(conflict.Consistency).String(), + }, nil + } + } tracking.AddPatientToRecentActivity(ctx, patientID.String()) return &pb.UpdatePatientResponse{ - Conflict: nil, // TODO + Conflict: nil, Consistency: consistency.String(), }, nil } diff --git a/services/tasks-svc/internal/patient/commands/v1/update_patient.go b/services/tasks-svc/internal/patient/commands/v1/update_patient.go index 5f106f556..b216f2bf1 100644 --- a/services/tasks-svc/internal/patient/commands/v1/update_patient.go +++ b/services/tasks-svc/internal/patient/commands/v1/update_patient.go @@ -6,29 +6,46 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/patient/aggregate" + "tasks-svc/internal/patient/models" ) -type UpdatePatientCommandHandler func(ctx context.Context, patientID uuid.UUID, humanReadableIdentifier *string, notes *string) (common.ConsistencyToken, error) +type UpdatePatientConflict struct { + Consistency uint64 + Was *models.Patient + Is *models.Patient +} + +type UpdatePatientCommandHandler func(ctx context.Context, patientID uuid.UUID, expConsistency *uint64, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) func NewUpdatePatientCommandHandler(as hwes.AggregateStore) UpdatePatientCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, humanReadableIdentifier *string, notes *string) (common.ConsistencyToken, error) { - a, err := aggregate.LoadPatientAggregate(ctx, as, patientID) + return func(ctx context.Context, patientID uuid.UUID, expConsistency *uint64, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) { + a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expConsistency) if err != nil { - return 0, err + return 0, nil, err + } + + // an update was performed since expConsistency + if expConsistency != nil && *expConsistency != a.GetVersion() { + return 0, &UpdatePatientConflict{ + Consistency: a.GetVersion(), + Was: oldState, + Is: a.Patient, + }, nil } if humanReadableIdentifier != nil { if err := a.UpdateHumanReadableIdentifier(ctx, *humanReadableIdentifier); err != nil { - return 0, err + return 0, nil, err } } if notes != nil { if err := a.UpdateNotes(ctx, *notes); err != nil { - return 0, err + return 0, nil, err } } - return as.Save(ctx, a) + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/tasks-svc/stories/PatientCRUD_test.go b/services/tasks-svc/stories/PatientCRUD_test.go index 6996ef9f8..6490899e2 100644 --- a/services/tasks-svc/stories/PatientCRUD_test.go +++ b/services/tasks-svc/stories/PatientCRUD_test.go @@ -90,6 +90,7 @@ func TestCreateUpdateGetPatient(t *testing.T) { readmitRes, err := patientClient.ReadmitPatient(ctx, &pb.ReadmitPatientRequest{PatientId: patientId}) assert.NoError(t, err) assert.NotEqual(t, getPatientRes.Consistency, readmitRes.Consistency) + time.Sleep(time.Millisecond * 100) // // get re-admitted patient @@ -681,3 +682,61 @@ func TestGetRecentPatients(t *testing.T) { assert.NotContains(t, foundIds, ids[0]) // thrown out } + +func TestUpdatePatientConflict(t *testing.T) { + ctx := context.Background() + patientClient := patientServiceClient() + + A := "A" + B := "B" + C := "C" + + testMatrix := []struct { + was string + is string + want *string + expectConflict bool + }{ + {A, B, nil, false}, + {A, B, &B, false}, + {A, B, &C, true}, + {A, A, &C, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+" "+strconv.Itoa(i), func(t *testing.T) { + // WAS + patientRes, err := patientClient.CreatePatient(ctx, &pb.CreatePatientRequest{ + HumanReadableIdentifier: o.was, + Notes: hwutil.PtrTo("A patient for test " + t.Name()), + }) + assert.NoError(t, err) + + id := patientRes.Id + initialConsistency := patientRes.Consistency + time.Sleep(time.Millisecond * 100) + + // IS + _, err = patientClient.UpdatePatient(ctx, &pb.UpdatePatientRequest{ + Id: id, + HumanReadableIdentifier: &o.is, + Notes: hwutil.PtrTo("Update"), + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + time.Sleep(time.Millisecond * 100) + + // WANT + updateRes, err := patientClient.UpdatePatient(ctx, &pb.UpdatePatientRequest{ + Id: id, + HumanReadableIdentifier: o.want, + Notes: hwutil.PtrTo("Update"), + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + }) + } +} From f67c11d62c6019985e75c469163512e56a932a03 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 27 Sep 2024 09:45:57 +0200 Subject: [PATCH 07/42] assign beds --- proto/libs/common/v1/conflict.proto | 2 +- .../tasks-svc/internal/patient/api/grpc.go | 81 ++++++++++++++-- .../patient/commands/v1/assign_bed.go | 30 ++++-- services/tasks-svc/internal/util/conflict.go | 21 ++-- .../tasks-svc/stories/PatientCRUD_test.go | 97 +++++++++++++++++++ 5 files changed, 209 insertions(+), 22 deletions(-) diff --git a/proto/libs/common/v1/conflict.proto b/proto/libs/common/v1/conflict.proto index 323d86fb9..f300a521f 100644 --- a/proto/libs/common/v1/conflict.proto +++ b/proto/libs/common/v1/conflict.proto @@ -21,6 +21,6 @@ message Conflict { } message AttributeConflict { - google.protobuf.Any is = 1; + google.protobuf.Any is = 1; // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) google.protobuf.Any want = 2; } diff --git a/services/tasks-svc/internal/patient/api/grpc.go b/services/tasks-svc/internal/patient/api/grpc.go index 44c86ca95..b4e893300 100644 --- a/services/tasks-svc/internal/patient/api/grpc.go +++ b/services/tasks-svc/internal/patient/api/grpc.go @@ -10,6 +10,7 @@ import ( zlog "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwdb/locale" @@ -393,11 +394,23 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa return nil, common.UnparsableConsistencyError(ctx, "consistency") } - consistency, conflict, err := s.handlers.Commands.V1.UpdatePatient(ctx, patientID, expConsistency, req.HumanReadableIdentifier, req.Notes) - if err != nil { - return nil, err - } - if conflict != nil { + var consistency common.ConsistencyToken + + for i := 0; true; i++ { + if i > 10 { + zlog.Ctx(ctx).Warn().Msg("UpdatePatient: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.UpdatePatient(ctx, patientID, expConsistency, req.HumanReadableIdentifier, req.Notes) + consistency = c + + if err != nil { + return nil, err + } + if conflict == nil { + break + } conflicts := make(map[string]*commonpb.AttributeConflict) // TODO: find a generic approach @@ -431,6 +444,9 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa Consistency: common.ConsistencyToken(conflict.Consistency).String(), }, nil } + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } tracking.AddPatientToRecentActivity(ctx, patientID.String()) @@ -456,9 +472,56 @@ func (s *PatientGrpcService) AssignBed(ctx context.Context, req *pb.AssignBedReq return nil, err } - consistency, err := s.handlers.Commands.V1.AssignBed(ctx, patientID, bedID) - if err != nil { - return nil, err + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + var consistency common.ConsistencyToken + + for i := 0; true; i++ { + if i > 10 { + log.Warn().Msg("AssignBed: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.AssignBed(ctx, patientID, bedID, expConsistency) + consistency = c + + if err != nil { + return nil, err + } + if conflict == nil { + break + } + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + bedUpdateRequested := req.BedId != conflict.Is.BedID.UUID.String() + bedAlreadyUpdated := conflict.Was.BedID != conflict.Is.BedID + if bedUpdateRequested && bedAlreadyUpdated { + var is proto.Message = nil + if conflict.Is.BedID.Valid { + is = wrapperspb.String(conflict.Is.BedID.UUID.String()) + } + conflicts["bed_id"], err = util.AttributeConflict( + is, + wrapperspb.String(req.BedId), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + return &pb.AssignBedResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: common.ConsistencyToken(conflict.Consistency).String(), + }, nil + } + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } log.Info().Str("patientID", patientID.String()).Str("bedID", bedID.String()).Msg("assigned bed to patient") @@ -466,7 +529,7 @@ func (s *PatientGrpcService) AssignBed(ctx context.Context, req *pb.AssignBedReq tracking.AddWardToRecentActivity(ctx, patientID.String()) return &pb.AssignBedResponse{ - Conflict: nil, // TODO + Conflict: nil, Consistency: consistency.String(), }, nil } diff --git a/services/tasks-svc/internal/patient/commands/v1/assign_bed.go b/services/tasks-svc/internal/patient/commands/v1/assign_bed.go index fc518c93c..a27957ca9 100644 --- a/services/tasks-svc/internal/patient/commands/v1/assign_bed.go +++ b/services/tasks-svc/internal/patient/commands/v1/assign_bed.go @@ -6,20 +6,38 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/patient/aggregate" + "tasks-svc/internal/patient/models" ) -type AssignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID) (common.ConsistencyToken, error) +type AssignBedConflict struct { + Consistency uint64 + Was *models.Patient + Is *models.Patient +} + +type AssignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignBedConflict, error) func NewAssignBedCommandHandler(as hwes.AggregateStore) AssignBedCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID) (common.ConsistencyToken, error) { - a, err := aggregate.LoadPatientAggregate(ctx, as, patientID) + return func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignBedConflict, error) { + a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expConsistency) if err != nil { - return 0, err + return 0, nil, err + } + + // update happened since + if expConsistency != nil && *expConsistency != a.GetVersion() { + return 0, &AssignBedConflict{ + Consistency: a.GetVersion(), + Was: oldState, + Is: a.Patient, + }, nil } if err := a.AssignBed(ctx, bedID); err != nil { - return 0, err + return 0, nil, err } - return as.Save(ctx, a) + + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/tasks-svc/internal/util/conflict.go b/services/tasks-svc/internal/util/conflict.go index 9bc88dd9e..8b429b766 100644 --- a/services/tasks-svc/internal/util/conflict.go +++ b/services/tasks-svc/internal/util/conflict.go @@ -10,13 +10,22 @@ import ( // AttributeConflict is a constructor for commonpb.AttributeConflicts // I'd love to move this somewhere else, but I also don't want common to depend on gen (and thus hwdb, hwes, ...) func AttributeConflict(is, want proto.Message) (*commonpb.AttributeConflict, error) { - wantAny, err := anypb.New(want) - if err != nil { - return nil, fmt.Errorf("AttributeConflict could not marshal want: %w", err) + var err error + + var wantAny *anypb.Any + if want != nil { + wantAny, err = anypb.New(want) + if err != nil { + return nil, fmt.Errorf("AttributeConflict could not marshal want: %w", err) + } } - isAny, err := anypb.New(is) - if err != nil { - return nil, fmt.Errorf("AttributeConflict could not marshal is: %w", err) + + var isAny *anypb.Any + if is != nil { + isAny, err = anypb.New(is) + if err != nil { + return nil, fmt.Errorf("AttributeConflict could not marshal is: %w", err) + } } return &commonpb.AttributeConflict{ diff --git a/services/tasks-svc/stories/PatientCRUD_test.go b/services/tasks-svc/stories/PatientCRUD_test.go index 6490899e2..f90aae772 100644 --- a/services/tasks-svc/stories/PatientCRUD_test.go +++ b/services/tasks-svc/stories/PatientCRUD_test.go @@ -737,6 +737,103 @@ func TestUpdatePatientConflict(t *testing.T) { // EXPECT assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["human_readable_identifier"] + assert.NotNil(t, conflict) + exp := "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"B\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"C\"}}" + assert.Equal(t, exp, conflict.String()) + } + }) + } +} + +func TestAssignBedConflict(t *testing.T) { + ctx := context.Background() + patientClient := patientServiceClient() + + wardId, _ := prepareWard(t, ctx, "") + roomId, _ := prepareRoom(t, ctx, wardId, "") + + A, _ := prepareBed(t, ctx, roomId, "A") + B, _ := prepareBed(t, ctx, roomId, "B") + C, _ := prepareBed(t, ctx, roomId, "C") + + testMatrix := []struct { + was string + is *string + want string + expectConflict bool + }{ + {A, &B, B, false}, + {A, &B, C, true}, + {A, &A, C, false}, + {A, nil, C, true}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + patientRes, err := patientClient.CreatePatient(ctx, &pb.CreatePatientRequest{ + HumanReadableIdentifier: t.Name(), + Notes: hwutil.PtrTo("A patient for test " + t.Name()), + }) + assert.NoError(t, err) + time.Sleep(time.Millisecond * 100) + + initialAssignment, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ + Id: patientRes.Id, + BedId: o.was, + Consistency: &patientRes.Consistency, + }) + assert.NoError(t, err) + assert.Nil(t, initialAssignment.Conflict) + + id := patientRes.Id + initialConsistency := initialAssignment.Consistency + + time.Sleep(time.Millisecond * 100) + + // IS + if o.is != nil { + a, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ + Id: id, + BedId: *o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, a.Conflict) + } else { + u, err := patientClient.UnassignBed(ctx, &pb.UnassignBedRequest{ + Id: id, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, u.Conflict) + } + time.Sleep(time.Millisecond * 100) + + // WANT + updateRes, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ + Id: id, + BedId: o.want, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["bed_id"] + assert.NotNil(t, conflict) + + exp := "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" + if o.is != nil { + exp = "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + *o.is + "\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" + } + assert.Equal(t, exp, conflict.String()) + } }) } } From c5ba38a1058af1dad44db020e68cb8b520495e7d Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 27 Sep 2024 09:58:47 +0200 Subject: [PATCH 08/42] unassign --- gen/go/libs/common/v1/conflict.pb.go | 4 +- proto/libs/common/v1/conflict.proto | 2 +- .../tasks-svc/internal/patient/api/grpc.go | 46 +++++++++- .../patient/commands/v1/unassign_bed.go | 29 +++++-- .../tasks-svc/stories/PatientCRUD_test.go | 84 +++++++++++++++++++ 5 files changed, 153 insertions(+), 12 deletions(-) diff --git a/gen/go/libs/common/v1/conflict.pb.go b/gen/go/libs/common/v1/conflict.pb.go index ef423c021..a40b6a69e 100644 --- a/gen/go/libs/common/v1/conflict.pb.go +++ b/gen/go/libs/common/v1/conflict.pb.go @@ -91,8 +91,8 @@ type AttributeConflict struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Is *anypb.Any `protobuf:"bytes,1,opt,name=is,proto3" json:"is,omitempty"` - Want *anypb.Any `protobuf:"bytes,2,opt,name=want,proto3" json:"want,omitempty"` + Is *anypb.Any `protobuf:"bytes,1,opt,name=is,proto3" json:"is,omitempty"` // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) + Want *anypb.Any `protobuf:"bytes,2,opt,name=want,proto3" json:"want,omitempty"` // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) } func (x *AttributeConflict) Reset() { diff --git a/proto/libs/common/v1/conflict.proto b/proto/libs/common/v1/conflict.proto index f300a521f..6d95ecd95 100644 --- a/proto/libs/common/v1/conflict.proto +++ b/proto/libs/common/v1/conflict.proto @@ -22,5 +22,5 @@ message Conflict { message AttributeConflict { google.protobuf.Any is = 1; // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) - google.protobuf.Any want = 2; + google.protobuf.Any want = 2; // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) } diff --git a/services/tasks-svc/internal/patient/api/grpc.go b/services/tasks-svc/internal/patient/api/grpc.go index b4e893300..6148f3071 100644 --- a/services/tasks-svc/internal/patient/api/grpc.go +++ b/services/tasks-svc/internal/patient/api/grpc.go @@ -544,9 +544,49 @@ func (s *PatientGrpcService) UnassignBed(ctx context.Context, req *pb.UnassignBe return nil, err } - consistency, err := s.handlers.Commands.V1.UnassignBed(ctx, patientID) - if err != nil { - return nil, err + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + var consistency uint64 + + for i := 0; true; i++ { + if i > 10 { + log.Warn().Msg("AssignBed: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.UnassignBed(ctx, patientID, expConsistency) + if err != nil { + return nil, err + } + consistency = c + if conflict == nil { + break + } + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + if conflict.Is.BedID.Valid && conflict.Was.BedID != conflict.Is.BedID { + conflicts["bed_id"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.BedID.UUID.String()), + nil, + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + return &pb.UnassignBedResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: strconv.FormatUint(conflict.Consistency, 10), + }, nil + } + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } log.Info().Str("patientID", patientID.String()).Msg("unassigned bed from patient") diff --git a/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go b/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go index 36dd7c3e7..33d5ce076 100644 --- a/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go +++ b/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go @@ -6,20 +6,37 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/patient/aggregate" + "tasks-svc/internal/patient/models" ) -type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID) (common.ConsistencyToken, error) +type UnassignBedConflict struct { + Consistency uint64 + Was *models.Patient + Is *models.Patient +} + +type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, expectedConsistency *uint64) (common.ConsistencyToken, *UnassignBedConflict, error) func NewUnassignBedCommandHandler(as hwes.AggregateStore) UnassignBedCommandHandler { - return func(ctx context.Context, patientID uuid.UUID) (common.ConsistencyToken, error) { - a, err := aggregate.LoadPatientAggregate(ctx, as, patientID) + return func(ctx context.Context, patientID uuid.UUID, expectedConsistency *uint64) (common.ConsistencyToken, *UnassignBedConflict, error) { + a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expectedConsistency) if err != nil { - return 0, err + return 0, nil, err + } + + // update has happened since + if expectedConsistency != nil && *expectedConsistency != a.GetVersion() { + return 0, &UnassignBedConflict{ + Consistency: a.GetVersion(), + Was: oldState, + Is: a.Patient, + }, nil } if err := a.UnassignBed(ctx); err != nil { - return 0, err + return 0, nil, err } - return as.Save(ctx, a) + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/tasks-svc/stories/PatientCRUD_test.go b/services/tasks-svc/stories/PatientCRUD_test.go index f90aae772..8cc028b4a 100644 --- a/services/tasks-svc/stories/PatientCRUD_test.go +++ b/services/tasks-svc/stories/PatientCRUD_test.go @@ -837,3 +837,87 @@ func TestAssignBedConflict(t *testing.T) { }) } } + +func TestUnassignBedConflict(t *testing.T) { + ctx := context.Background() + patientClient := patientServiceClient() + + wardId, _ := prepareWard(t, ctx, "") + roomId, _ := prepareRoom(t, ctx, wardId, "") + + A, _ := prepareBed(t, ctx, roomId, "A") + B, _ := prepareBed(t, ctx, roomId, "B") + + testMatrix := []struct { + was string + is *string + expectConflict bool + }{ + {A, &B, true}, + {A, &A, false}, + {A, nil, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + patientRes, err := patientClient.CreatePatient(ctx, &pb.CreatePatientRequest{ + HumanReadableIdentifier: t.Name(), + Notes: hwutil.PtrTo("A patient for test " + t.Name()), + }) + assert.NoError(t, err) + time.Sleep(time.Millisecond * 100) + + initialAssignment, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ + Id: patientRes.Id, + BedId: o.was, + Consistency: &patientRes.Consistency, + }) + assert.NoError(t, err) + assert.Nil(t, initialAssignment.Conflict) + + id := patientRes.Id + initialConsistency := initialAssignment.Consistency + + time.Sleep(time.Millisecond * 100) + + // IS + if o.is != nil { + a, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ + Id: id, + BedId: *o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, a.Conflict) + } else { + u, err := patientClient.UnassignBed(ctx, &pb.UnassignBedRequest{ + Id: id, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, u.Conflict) + } + time.Sleep(time.Millisecond * 100) + + // WANT + updateRes, err := patientClient.UnassignBed(ctx, &pb.UnassignBedRequest{ + Id: id, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["bed_id"] + assert.NotNil(t, conflict) + exp := "" + if o.is != nil { + exp = "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + *o.is + "\"}}" + } + assert.Equal(t, exp, conflict.String()) + } + }) + } +} From be37bf972d2a3aa992a8a4f5d81ae58e570329c3 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 27 Sep 2024 15:05:21 +0200 Subject: [PATCH 09/42] UpdateTask --- .../internal/task/aggregate/aggregate.go | 22 ++ services/tasks-svc/internal/task/api/grpc.go | 112 ++++++- .../internal/task/commands/v1/update_task.go | 45 ++- .../tasks-svc/stories/PatientCRUD_test.go | 10 +- .../tasks-svc/stories/TaskConflicts_test.go | 302 ++++++++++++++++++ 5 files changed, 474 insertions(+), 17 deletions(-) create mode 100644 services/tasks-svc/stories/TaskConflicts_test.go diff --git a/services/tasks-svc/internal/task/aggregate/aggregate.go b/services/tasks-svc/internal/task/aggregate/aggregate.go index 5f826233f..4dc0761c0 100644 --- a/services/tasks-svc/internal/task/aggregate/aggregate.go +++ b/services/tasks-svc/internal/task/aggregate/aggregate.go @@ -39,6 +39,28 @@ func LoadTaskAggregate(ctx context.Context, as hwes.AggregateStore, id uuid.UUID return taskAggregate, nil } +func LoadTaskAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *uint64) (*TaskAggregate, *models.Task, error) { + taskAggregate := NewTaskAggregate(id) + var snapshot *models.Task + + if pauseAt != nil { + // load pauseAt+1-many events (version is 0-indexed) + if err := as.LoadN(ctx, taskAggregate, *pauseAt+1); err != nil { + return nil, nil, err + } + + task := *taskAggregate.Task // deref copies model + snapshot = &task + } + + // continue loading all other events + if err := as.Load(ctx, taskAggregate); err != nil { + return nil, nil, err + } + + return taskAggregate, snapshot, nil +} + func (a *TaskAggregate) initEventListeners() { a. RegisterEventListener(taskEventsV1.TaskCreated, a.onTaskCreated). diff --git a/services/tasks-svc/internal/task/api/grpc.go b/services/tasks-svc/internal/task/api/grpc.go index 16fadf62a..b149fad96 100644 --- a/services/tasks-svc/internal/task/api/grpc.go +++ b/services/tasks-svc/internal/task/api/grpc.go @@ -3,14 +3,21 @@ package api import ( "common" "context" + "fmt" + commonpb "gen/libs/common/v1" pb "gen/services/tasks_svc/v1" "github.com/google/uuid" + zlog "github.com/rs/zerolog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "hwes" "hwutil" "tasks-svc/internal/task/handlers" + "tasks-svc/internal/util" + "time" ) type TaskGrpcService struct { @@ -60,15 +67,114 @@ func (s *TaskGrpcService) CreateTask(ctx context.Context, req *pb.CreateTaskRequ }, nil } +func timeAlreadyUpdated(was, is *time.Time) bool { + if was != nil && is != nil { + return !was.Round(time.Second).Equal(is.Round(time.Second)) + } + + return true +} + func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequest) (*pb.UpdateTaskResponse, error) { + log := zlog.Ctx(ctx) + taskID, err := uuid.Parse(req.GetId()) if err != nil { return nil, err } - consistency, err := s.handlers.Commands.V1.UpdateTask(ctx, taskID, req.Name, req.Description, req.Status, req.Public, req.DueAt) - if err != nil { - return nil, err + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + var consistency uint64 + + for i := 0; true; i++ { + if i > 10 { + log.Warn().Msg("UpdatePatient: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.UpdateTask(ctx, + taskID, req.Name, req.Description, req.Status, req.Public, req.DueAt, expConsistency) + if err != nil { + return nil, err + } + consistency = c + + if conflict == nil { + break + } + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + nameUpdateRequested := req.Name != nil && *req.Name != conflict.Is.Name + nameAlreadyUpdated := conflict.Was.Name != conflict.Is.Name + if nameUpdateRequested && nameAlreadyUpdated { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.Name), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + + descrUpdateRequested := req.Description != nil && *req.Description != conflict.Is.Description + descrAlreadyUpdated := conflict.Was.Description != conflict.Is.Description + if descrUpdateRequested && descrAlreadyUpdated { + conflicts["description"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.Description), + wrapperspb.String(*req.Description), + ) + if err != nil { + return nil, fmt.Errorf("could not marshall description conflict: %w", err) + } + } + + dueUpdateRequested := req.DueAt != nil && + (conflict.Is.DueAt == nil || !req.DueAt.AsTime().Round(time.Second).Equal(conflict.Is.DueAt.Round(time.Second))) + dueAlreadyUpdated := timeAlreadyUpdated(conflict.Was.DueAt, conflict.Is.DueAt) + if dueUpdateRequested && dueAlreadyUpdated { + var is proto.Message = nil + if conflict.Is.DueAt != nil { + is = timestamppb.New(*conflict.Is.DueAt) + } + conflicts["due_at"], err = util.AttributeConflict( + is, + req.DueAt, + ) + if err != nil { + return nil, fmt.Errorf("could not marshall due_at conflict: %w", err) + } + } + + statusUpdateRequested := req.Status != nil && *req.Status != conflict.Is.Status + statusAlreadyUpdated := conflict.Was.Status != conflict.Is.Status + if statusUpdateRequested && statusAlreadyUpdated { + conflicts["status"], err = util.AttributeConflict( + wrapperspb.Int32(int32(conflict.Is.Status)), + wrapperspb.Int32(int32(*req.Status)), + ) + if err != nil { + return nil, fmt.Errorf("could not marshall status conflict: %w", err) + } + } + + // bool public can never cause a problem + // the user expects public = B, and sets it to \neg B + // so either that is the case still, or the update will do nothing anyway + + if len(conflicts) != 0 { + return &pb.UpdateTaskResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: strconv.FormatUint(conflict.Consistency, 10), + }, nil + } + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } return &pb.UpdateTaskResponse{ diff --git a/services/tasks-svc/internal/task/commands/v1/update_task.go b/services/tasks-svc/internal/task/commands/v1/update_task.go index dc5fa4615..e9eecf28c 100644 --- a/services/tasks-svc/internal/task/commands/v1/update_task.go +++ b/services/tasks-svc/internal/task/commands/v1/update_task.go @@ -8,8 +8,15 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "hwes" "tasks-svc/internal/task/aggregate" + "tasks-svc/internal/task/models" ) +type UpdateTaskConflict struct { + Consistency uint64 + Was *models.Task + Is *models.Task +} + type UpdateTaskCommandHandler func( ctx context.Context, taskID uuid.UUID, @@ -18,45 +25,63 @@ type UpdateTaskCommandHandler func( status *pb.TaskStatus, public *bool, dueAt *timestamppb.Timestamp, -) (common.ConsistencyToken, error) + expConsistency *uint64, +) (common.ConsistencyToken, *UpdateTaskConflict, error) func NewUpdateTaskCommandHandler(as hwes.AggregateStore) UpdateTaskCommandHandler { - return func(ctx context.Context, taskID uuid.UUID, name *string, description *string, status *pb.TaskStatus, public *bool, dueAt *timestamppb.Timestamp) (common.ConsistencyToken, error) { - a, err := aggregate.LoadTaskAggregate(ctx, as, taskID) + return func(ctx context.Context, + taskID uuid.UUID, + name *string, + description *string, + status *pb.TaskStatus, + public *bool, + dueAt *timestamppb.Timestamp, + expConsistency *uint64, + ) (common.ConsistencyToken, *UpdateTaskConflict, error) { + a, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { - return 0, err + return 0, nil, err + } + + if expConsistency != nil && *expConsistency != a.GetVersion() { + return 0, &UpdateTaskConflict{ + Consistency: a.GetVersion(), + Was: oldState, + Is: a.Task, + }, nil } if name != nil { if err := a.UpdateName(ctx, *name); err != nil { - return 0, err + return 0, nil, err } } if description != nil { if err := a.UpdateDescription(ctx, *description); err != nil { - return 0, err + return 0, nil, err } } if status != nil { if err := a.UpdateStatus(ctx, *status); err != nil { - return 0, err + return 0, nil, err } } if public != nil && a.Task.Public != *public { if err := a.UpdateTaskPublic(ctx, *public); err != nil { - return 0, err + return 0, nil, err } } if dueAt != nil { if err := a.UpdateDueAt(ctx, dueAt.AsTime()); err != nil { - return 0, err + return 0, nil, err } } - return as.Save(ctx, a) + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/tasks-svc/stories/PatientCRUD_test.go b/services/tasks-svc/stories/PatientCRUD_test.go index 8cc028b4a..3e92953f8 100644 --- a/services/tasks-svc/stories/PatientCRUD_test.go +++ b/services/tasks-svc/stories/PatientCRUD_test.go @@ -9,6 +9,7 @@ import ( "hwtesting" "hwutil" "strconv" + "strings" "testing" ) @@ -691,6 +692,7 @@ func TestUpdatePatientConflict(t *testing.T) { B := "B" C := "C" + // Only testing HumanReadableIdentifier testMatrix := []struct { was string is string @@ -740,9 +742,9 @@ func TestUpdatePatientConflict(t *testing.T) { if o.expectConflict { conflict := updateRes.Conflict.ConflictingAttributes["human_readable_identifier"] assert.NotNil(t, conflict) - exp := "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"B\"}} " + + exp := "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"B\"}} " + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"C\"}}" - assert.Equal(t, exp, conflict.String()) + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) } }) } @@ -829,10 +831,10 @@ func TestAssignBedConflict(t *testing.T) { exp := "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" if o.is != nil { - exp = "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + *o.is + "\"}} " + + exp = "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + *o.is + "\"}} " + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" } - assert.Equal(t, exp, conflict.String()) + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) } }) } diff --git a/services/tasks-svc/stories/TaskConflicts_test.go b/services/tasks-svc/stories/TaskConflicts_test.go new file mode 100644 index 000000000..83b57f9c4 --- /dev/null +++ b/services/tasks-svc/stories/TaskConflicts_test.go @@ -0,0 +1,302 @@ +package stories + +import ( + "context" + pb "gen/services/tasks_svc/v1" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" + "math" + "strconv" + "strings" + "testing" + "time" +) + +func TestUpdateTaskConflict_Name(t *testing.T) { + ctx := context.Background() + taskClient := taskServiceClient() + + patientId := preparePatient(t, ctx, "") + + A := "A" + B := "B" + C := "C" + + // Only testing HumanReadableIdentifier + testMatrix := []struct { + was string + is string + want *string + expectConflict bool + }{ + {A, B, nil, false}, + {A, B, &B, false}, + {A, B, &C, true}, + {A, A, &C, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + task, err := taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ + Name: o.was, + Description: nil, + PatientId: patientId, + Public: nil, + DueAt: nil, + InitialStatus: nil, + AssignedUserId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + id := task.Id + initialConsistency := task.Consistency + + // IS + _, err = taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + Name: &o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // WANT + updateRes, err := taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + Name: o.want, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, conflict) + exp := "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"B\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"C\"}}" + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) + } + }) + } +} + +func TestUpdateTaskConflict_Description(t *testing.T) { + ctx := context.Background() + taskClient := taskServiceClient() + + patientId := preparePatient(t, ctx, "") + + A := "A" + B := "B" + C := "C" + + // Only testing HumanReadableIdentifier + testMatrix := []struct { + was string + is string + want *string + expectConflict bool + }{ + {A, B, nil, false}, + {A, B, &B, false}, + {A, B, &C, true}, + {A, A, &C, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + task, err := taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ + Name: t.Name(), + Description: &o.was, + PatientId: patientId, + Public: nil, + DueAt: nil, + InitialStatus: nil, + AssignedUserId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + id := task.Id + initialConsistency := task.Consistency + + // IS + _, err = taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + Description: &o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // WANT + updateRes, err := taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + Description: o.want, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["description"] + assert.NotNil(t, conflict) + exp := "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"B\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"C\"}}" + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) + } + }) + } +} + +func TestUpdateTaskConflict_DueAt(t *testing.T) { + ctx := context.Background() + taskClient := taskServiceClient() + + patientId := preparePatient(t, ctx, "") + + A := timestamppb.New(time.Now()) + B := timestamppb.New(time.Now().Add(time.Hour)) + C := timestamppb.New(time.Now().Add(time.Hour * 2)) + + // Only testing HumanReadableIdentifier + testMatrix := []struct { + was *timestamppb.Timestamp + is *timestamppb.Timestamp + want *timestamppb.Timestamp + expectConflict bool + }{ + {A, B, nil, false}, + {A, B, B, false}, + {A, B, C, true}, + {A, A, C, false}, + {nil, B, C, true}, + {nil, C, C, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + task, err := taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ + Name: t.Name(), + Description: nil, + PatientId: patientId, + Public: nil, + DueAt: o.was, + InitialStatus: nil, + AssignedUserId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + id := task.Id + initialConsistency := task.Consistency + + // IS + _, err = taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + DueAt: o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // WANT + updateRes, err := taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + DueAt: o.want, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["due_at"] + assert.NotNil(t, conflict) + exp := "is:{[type.googleapis.com/google.protobuf.Timestamp]:{" + o.is.String() + "}} " + + "want:{[type.googleapis.com/google.protobuf.Timestamp]:{" + o.want.String() + "}}" + assert.Equal(t, + strings.Replace(exp, " ", " ", math.MaxInt), + strings.Replace(conflict.String(), " ", " ", math.MaxInt), + ) + } + }) + } +} + +func TestUpdateTaskConflict_Status(t *testing.T) { + ctx := context.Background() + taskClient := taskServiceClient() + + patientId := preparePatient(t, ctx, "") + + A := pb.TaskStatus_TASK_STATUS_TODO + B := pb.TaskStatus_TASK_STATUS_IN_PROGRESS + C := pb.TaskStatus_TASK_STATUS_DONE + + // Only testing HumanReadableIdentifier + testMatrix := []struct { + was pb.TaskStatus + is *pb.TaskStatus + want *pb.TaskStatus + expectConflict bool + }{ + {A, &B, nil, false}, + {A, &B, &B, false}, + {A, &B, &C, true}, + {A, &A, &C, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + task, err := taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ + Name: t.Name(), + Description: nil, + PatientId: patientId, + Public: nil, + DueAt: nil, + InitialStatus: &o.was, + AssignedUserId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + id := task.Id + initialConsistency := task.Consistency + + // IS + _, err = taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + Status: o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // WANT + updateRes, err := taskClient.UpdateTask(ctx, &pb.UpdateTaskRequest{ + Id: id, + Status: o.want, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["status"] + assert.NotNil(t, conflict) + exp := "want:{[type.googleapis.com/google.protobuf.Int32Value]:{value:" + strconv.Itoa(int(*o.want)) + "}}" + + if o.is != nil { + exp = "is:{[type.googleapis.com/google.protobuf.Int32Value]:{value:" + strconv.Itoa(int(*o.is)) + "}} " + + "want:{[type.googleapis.com/google.protobuf.Int32Value]:{value:" + strconv.Itoa(int(*o.want)) + "}}" + } + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) + } + }) + } +} From 309515639c318cc8bd515b511cb4c499c13a7934 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 27 Sep 2024 15:29:43 +0200 Subject: [PATCH 10/42] assigntask --- gen/dart/lib/libs/common/v1/conflict.pb.dart | 4 + gen/go/libs/common/v1/conflict.pb.go | 8 +- proto/libs/common/v1/conflict.proto | 8 +- services/tasks-svc/internal/task/api/grpc.go | 53 ++++++++++- .../internal/task/commands/v1/assign_task.go | 28 ++++-- .../tasks-svc/stories/TaskConflicts_test.go | 89 ++++++++++++++++++- 6 files changed, 173 insertions(+), 17 deletions(-) diff --git a/gen/dart/lib/libs/common/v1/conflict.pb.dart b/gen/dart/lib/libs/common/v1/conflict.pb.dart index 56faf470d..e45c367a4 100644 --- a/gen/dart/lib/libs/common/v1/conflict.pb.dart +++ b/gen/dart/lib/libs/common/v1/conflict.pb.dart @@ -127,6 +127,8 @@ class AttributeConflict extends $pb.GeneratedMessage { static AttributeConflict getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static AttributeConflict? _defaultInstance; + /// CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) + /// Enums are returned as Int32s @$pb.TagNumber(1) $19.Any get is_1 => $_getN(0); @$pb.TagNumber(1) @@ -138,6 +140,8 @@ class AttributeConflict extends $pb.GeneratedMessage { @$pb.TagNumber(1) $19.Any ensureIs_1() => $_ensure(0); + /// CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) + /// Enums are returned as Int32s @$pb.TagNumber(2) $19.Any get want => $_getN(1); @$pb.TagNumber(2) diff --git a/gen/go/libs/common/v1/conflict.pb.go b/gen/go/libs/common/v1/conflict.pb.go index a40b6a69e..c2831d86f 100644 --- a/gen/go/libs/common/v1/conflict.pb.go +++ b/gen/go/libs/common/v1/conflict.pb.go @@ -91,8 +91,12 @@ type AttributeConflict struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Is *anypb.Any `protobuf:"bytes,1,opt,name=is,proto3" json:"is,omitempty"` // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) - Want *anypb.Any `protobuf:"bytes,2,opt,name=want,proto3" json:"want,omitempty"` // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) + // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) + // Enums are returned as Int32s + Is *anypb.Any `protobuf:"bytes,1,opt,name=is,proto3" json:"is,omitempty"` + // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) + // Enums are returned as Int32s + Want *anypb.Any `protobuf:"bytes,2,opt,name=want,proto3" json:"want,omitempty"` } func (x *AttributeConflict) Reset() { diff --git a/proto/libs/common/v1/conflict.proto b/proto/libs/common/v1/conflict.proto index 6d95ecd95..a5d5ca0d2 100644 --- a/proto/libs/common/v1/conflict.proto +++ b/proto/libs/common/v1/conflict.proto @@ -21,6 +21,10 @@ message Conflict { } message AttributeConflict { - google.protobuf.Any is = 1; // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) - google.protobuf.Any want = 2; // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) + // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) + // Enums are returned as Int32s + google.protobuf.Any is = 1; + // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) + // Enums are returned as Int32s + google.protobuf.Any want = 2; } diff --git a/services/tasks-svc/internal/task/api/grpc.go b/services/tasks-svc/internal/task/api/grpc.go index b149fad96..66846c895 100644 --- a/services/tasks-svc/internal/task/api/grpc.go +++ b/services/tasks-svc/internal/task/api/grpc.go @@ -8,6 +8,7 @@ import ( pb "gen/services/tasks_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -193,9 +194,55 @@ func (s *TaskGrpcService) AssignTask(ctx context.Context, req *pb.AssignTaskRequ return nil, err } - consistency, err := s.handlers.Commands.V1.AssignTask(ctx, taskID, userID) - if err != nil { - return nil, err + expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + var consistency uint64 + + for i := 0; true; i++ { + if i > 10 { + log.Warn().Msg("UpdatePatient: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.AssignTask(ctx, taskID, userID, expConsistency) + if err != nil { + return nil, err + } + consistency = c + if conflict == nil { + break + } + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + userUpdateRequested := req.UserId != conflict.Is.AssignedUser.UUID.String() + userAlreadyUpdated := conflict.Was.AssignedUser != conflict.Is.AssignedUser + if userUpdateRequested && userAlreadyUpdated { + var is proto.Message = nil + if conflict.Is.AssignedUser.Valid { + is = wrapperspb.String(conflict.Is.AssignedUser.UUID.String()) + } + conflicts["user_id"], err = util.AttributeConflict( + is, + wrapperspb.String(req.UserId), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + return &pb.AssignTaskResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: strconv.FormatUint(conflict.Consistency, 10), + }, nil + } + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } return &pb.AssignTaskResponse{ diff --git a/services/tasks-svc/internal/task/commands/v1/assign_task.go b/services/tasks-svc/internal/task/commands/v1/assign_task.go index 6172ac0a1..4ca07c873 100644 --- a/services/tasks-svc/internal/task/commands/v1/assign_task.go +++ b/services/tasks-svc/internal/task/commands/v1/assign_task.go @@ -6,22 +6,38 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/task/aggregate" + "tasks-svc/internal/task/models" ) -type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID) (common.ConsistencyToken, error) +type AssignTaskConflict struct { + Consistency uint64 + Was *models.Task + Is *models.Task +} + +type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignTaskConflict, error) func NewAssignTaskCommandHandler(as hwes.AggregateStore) AssignTaskCommandHandler { - return func(ctx context.Context, taskID, userID uuid.UUID) (common.ConsistencyToken, error) { - task, err := aggregate.LoadTaskAggregate(ctx, as, taskID) + return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignTaskConflict, error) { + task, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { - return 0, err + return 0, nil, err + } + + if expConsistency != nil && *expConsistency != task.GetVersion() { + return 0, &AssignTaskConflict{ + Consistency: task.GetVersion(), + Was: oldState, + Is: task.Task, + }, nil } // TODO: Handle SelfAssignTask when common.GetUserID() is testable if err := task.AssignTask(ctx, userID); err != nil { - return 0, err + return 0, nil, err } - return as.Save(ctx, task) + consistency, err := as.Save(ctx, task) + return consistency, nil, err } } diff --git a/services/tasks-svc/stories/TaskConflicts_test.go b/services/tasks-svc/stories/TaskConflicts_test.go index 83b57f9c4..e94af5013 100644 --- a/services/tasks-svc/stories/TaskConflicts_test.go +++ b/services/tasks-svc/stories/TaskConflicts_test.go @@ -3,6 +3,7 @@ package stories import ( "context" pb "gen/services/tasks_svc/v1" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/timestamppb" "math" @@ -22,7 +23,6 @@ func TestUpdateTaskConflict_Name(t *testing.T) { B := "B" C := "C" - // Only testing HumanReadableIdentifier testMatrix := []struct { was string is string @@ -92,7 +92,6 @@ func TestUpdateTaskConflict_Description(t *testing.T) { B := "B" C := "C" - // Only testing HumanReadableIdentifier testMatrix := []struct { was string is string @@ -162,7 +161,6 @@ func TestUpdateTaskConflict_DueAt(t *testing.T) { B := timestamppb.New(time.Now().Add(time.Hour)) C := timestamppb.New(time.Now().Add(time.Hour * 2)) - // Only testing HumanReadableIdentifier testMatrix := []struct { was *timestamppb.Timestamp is *timestamppb.Timestamp @@ -237,7 +235,6 @@ func TestUpdateTaskConflict_Status(t *testing.T) { B := pb.TaskStatus_TASK_STATUS_IN_PROGRESS C := pb.TaskStatus_TASK_STATUS_DONE - // Only testing HumanReadableIdentifier testMatrix := []struct { was pb.TaskStatus is *pb.TaskStatus @@ -300,3 +297,87 @@ func TestUpdateTaskConflict_Status(t *testing.T) { }) } } + +func TestAssignTaskConflict(t *testing.T) { + ctx := context.Background() + taskClient := taskServiceClient() + + patientId := preparePatient(t, ctx, "") + + A := uuid.New().String() + B := uuid.New().String() + C := uuid.New().String() + + testMatrix := []struct { + was *string + is *string + want string + expectConflict bool + }{ + {&A, &B, B, false}, + {&A, &B, C, true}, + {&A, &A, C, false}, + {nil, &A, C, true}, + {&A, nil, C, true}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + task, err := taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ + Name: t.Name(), + Description: nil, + PatientId: patientId, + Public: nil, + DueAt: nil, + InitialStatus: nil, + AssignedUserId: o.was, + Subtasks: nil, + }) + assert.NoError(t, err) + + id := task.Id + initialConsistency := task.Consistency + + // IS + if o.is != nil { + a, err := taskClient.AssignTask(ctx, &pb.AssignTaskRequest{ + TaskId: id, + UserId: *o.is, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, a.Conflict) + } else { + a, err := taskClient.UnassignTask(ctx, &pb.UnassignTaskRequest{ + TaskId: id, + UserId: *o.was, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, a.Conflict) + } + + // WANT + updateRes, err := taskClient.AssignTask(ctx, &pb.AssignTaskRequest{ + TaskId: id, + UserId: o.want, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["user_id"] + assert.NotNil(t, conflict) + exp := "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" + if o.is != nil { + exp = "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + *o.is + "\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" + } + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) + } + }) + } +} From 77855832c7b0e916dc5cc3e4bd40781c761b87a7 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 27 Sep 2024 17:25:05 +0200 Subject: [PATCH 11/42] updatesubtask --- .../services/tasks_svc/v1/task_svc.pb.dart | 14 - .../tasks_svc/v1/task_svc.pbjson.dart | 7 +- gen/go/services/tasks_svc/v1/task_svc.pb.go | 358 +++++++++--------- gen/ts/services/tasks_svc/v1/task_svc_pb.d.ts | 11 - gen/ts/services/tasks_svc/v1/task_svc_pb.js | 50 +-- proto/services/tasks_svc/v1/task_svc.proto | 1 - .../internal/task/aggregate/aggregate.go | 12 + services/tasks-svc/internal/task/api/grpc.go | 67 +++- .../internal/task/commands/v1/assign_task.go | 6 +- .../task/commands/v1/update_subtask.go | 25 +- services/tasks-svc/stories/TaskCRUD_test.go | 5 +- .../tasks-svc/stories/TaskConflicts_test.go | 91 ++++- 12 files changed, 359 insertions(+), 288 deletions(-) diff --git a/gen/dart/lib/services/tasks_svc/v1/task_svc.pb.dart b/gen/dart/lib/services/tasks_svc/v1/task_svc.pb.dart index 3bea64892..a90f31d18 100644 --- a/gen/dart/lib/services/tasks_svc/v1/task_svc.pb.dart +++ b/gen/dart/lib/services/tasks_svc/v1/task_svc.pb.dart @@ -2291,7 +2291,6 @@ class UnassignTaskRequest extends $pb.GeneratedMessage { factory UnassignTaskRequest({ $core.String? taskId, $core.String? userId, - $core.String? consistency, }) { final $result = create(); if (taskId != null) { @@ -2300,9 +2299,6 @@ class UnassignTaskRequest extends $pb.GeneratedMessage { if (userId != null) { $result.userId = userId; } - if (consistency != null) { - $result.consistency = consistency; - } return $result; } UnassignTaskRequest._() : super(); @@ -2312,7 +2308,6 @@ class UnassignTaskRequest extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'UnassignTaskRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'services.tasks_svc.v1'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'taskId') ..aOS(2, _omitFieldNames ? '' : 'userId') - ..aOS(3, _omitFieldNames ? '' : 'consistency') ..hasRequiredFields = false ; @@ -2354,15 +2349,6 @@ class UnassignTaskRequest extends $pb.GeneratedMessage { $core.bool hasUserId() => $_has(1); @$pb.TagNumber(2) void clearUserId() => clearField(2); - - @$pb.TagNumber(3) - $core.String get consistency => $_getSZ(2); - @$pb.TagNumber(3) - set consistency($core.String v) { $_setString(2, v); } - @$pb.TagNumber(3) - $core.bool hasConsistency() => $_has(2); - @$pb.TagNumber(3) - void clearConsistency() => clearField(3); } class UnassignTaskResponse extends $pb.GeneratedMessage { diff --git a/gen/dart/lib/services/tasks_svc/v1/task_svc.pbjson.dart b/gen/dart/lib/services/tasks_svc/v1/task_svc.pbjson.dart index 9aa1bb8ab..db144682c 100644 --- a/gen/dart/lib/services/tasks_svc/v1/task_svc.pbjson.dart +++ b/gen/dart/lib/services/tasks_svc/v1/task_svc.pbjson.dart @@ -476,18 +476,13 @@ const UnassignTaskRequest$json = { '2': [ {'1': 'task_id', '3': 1, '4': 1, '5': 9, '10': 'taskId'}, {'1': 'user_id', '3': 2, '4': 1, '5': 9, '10': 'userId'}, - {'1': 'consistency', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'consistency', '17': true}, - ], - '8': [ - {'1': '_consistency'}, ], }; /// Descriptor for `UnassignTaskRequest`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List unassignTaskRequestDescriptor = $convert.base64Decode( 'ChNVbmFzc2lnblRhc2tSZXF1ZXN0EhcKB3Rhc2tfaWQYASABKAlSBnRhc2tJZBIXCgd1c2VyX2' - 'lkGAIgASgJUgZ1c2VySWQSJQoLY29uc2lzdGVuY3kYAyABKAlIAFILY29uc2lzdGVuY3mIAQFC' - 'DgoMX2NvbnNpc3RlbmN5'); + 'lkGAIgASgJUgZ1c2VySWQ='); @$core.Deprecated('Use unassignTaskResponseDescriptor instead') const UnassignTaskResponse$json = { diff --git a/gen/go/services/tasks_svc/v1/task_svc.pb.go b/gen/go/services/tasks_svc/v1/task_svc.pb.go index 2680e4240..2ff796661 100644 --- a/gen/go/services/tasks_svc/v1/task_svc.pb.go +++ b/gen/go/services/tasks_svc/v1/task_svc.pb.go @@ -924,9 +924,8 @@ type UnassignTaskRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty" validate:"uuid4"` // @gotags: validate:"uuid4" - UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty" validate:"uuid4"` // @gotags: validate:"uuid4" - Consistency *string `protobuf:"bytes,3,opt,name=consistency,proto3,oneof" json:"consistency,omitempty"` // no conflict detection, if not provided + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty" validate:"uuid4"` // @gotags: validate:"uuid4" + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty" validate:"uuid4"` // @gotags: validate:"uuid4" } func (x *UnassignTaskRequest) Reset() { @@ -975,13 +974,6 @@ func (x *UnassignTaskRequest) GetUserId() string { return "" } -func (x *UnassignTaskRequest) GetConsistency() string { - if x != nil && x.Consistency != nil { - return *x.Consistency - } - return "" -} - type UnassignTaskResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2836,191 +2828,188 @@ var file_services_tasks_svc_v1_task_svc_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x0b, 0x0a, 0x09, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0x7e, 0x0a, 0x13, 0x55, 0x6e, 0x61, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0x47, 0x0a, 0x13, 0x55, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, - 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x63, 0x6f, - 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x80, 0x01, 0x0a, 0x14, 0x55, 0x6e, - 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x48, 0x00, - 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, - 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0xbf, 0x01, 0x0a, - 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x4d, - 0x0a, 0x07, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x33, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, - 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, - 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x75, 0x62, - 0x74, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x1a, 0x3f, 0x0a, - 0x07, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x04, - 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x04, 0x64, 0x6f, - 0x6e, 0x65, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x22, 0x61, - 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x74, 0x61, - 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, - 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x63, - 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x22, 0xb1, 0x02, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, - 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, - 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, - 0x6b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, - 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, - 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, - 0x6b, 0x12, 0x2e, 0x0a, 0x10, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0f, 0x74, - 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x88, 0x01, - 0x01, 0x1a, 0x4d, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x17, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x88, 0x01, 0x01, 0x42, 0x07, - 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x6f, 0x6e, 0x65, - 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x8a, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x48, 0x00, 0x52, 0x08, 0x63, - 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x61, - 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, - 0x63, 0x74, 0x22, 0x4e, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, - 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, - 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, - 0x6b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, - 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, - 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x18, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, - 0x22, 0x3d, 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, - 0x65, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, - 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, - 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, - 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc2, 0x0b, 0x0a, 0x0b, 0x54, - 0x61, 0x73, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x0a, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x49, 0x64, 0x22, 0x80, 0x01, 0x0a, 0x14, 0x55, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, + 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x63, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, + 0x69, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, + 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x74, 0x61, + 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x63, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, - 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, - 0x25, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x73, + 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x1a, 0x3f, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, + 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x88, 0x01, 0x01, 0x42, 0x07, + 0x0a, 0x05, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x22, 0x61, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, + 0x29, 0x0a, 0x10, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x43, + 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xb1, 0x02, 0x0a, 0x14, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x73, + 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, + 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, + 0x6b, 0x52, 0x07, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x2e, 0x0a, 0x10, 0x74, 0x61, + 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x73, + 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x88, 0x01, 0x01, 0x1a, 0x4d, 0x0a, 0x07, 0x53, 0x75, + 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, + 0x0a, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x04, + 0x64, 0x6f, 0x6e, 0x65, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x74, 0x61, + 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x8a, + 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x66, + 0x6c, 0x69, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, + 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x6c, 0x69, 0x63, 0x74, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x73, + 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, + 0x61, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x0b, + 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0x4e, 0x0a, 0x14, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x3d, 0x0a, 0x19, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, + 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xc2, 0x0b, 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, + 0x6b, 0x12, 0x28, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, + 0x07, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x25, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x78, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, - 0x74, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0xa2, 0x01, 0x0a, 0x1f, 0x47, - 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, - 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3d, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x78, 0x0a, 0x11, 0x47, 0x65, 0x74, + 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, - 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x42, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, + 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x30, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, + 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0xa2, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, + 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x42, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, + 0x74, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, + 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x75, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, + 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, + 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x63, 0x0a, 0x0a, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, + 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x69, 0x0a, 0x0c, 0x55, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, + 0x54, 0x61, 0x73, 0x6b, 0x12, 0x2a, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x61, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x6c, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, + 0x12, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, - 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x42, 0x79, - 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x42, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x75, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x61, - 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, - 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, - 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, - 0x54, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, - 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, + 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, + 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6c, 0x0a, + 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, - 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, - 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x69, 0x0a, 0x0c, 0x55, - 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x2a, 0x2e, 0x73, 0x65, + 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, + 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, - 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x6e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6c, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x6c, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, - 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x6c, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, - 0x61, 0x73, 0x6b, 0x12, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, - 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, - 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, - 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x78, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, - 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, - 0xb6, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x54, - 0x61, 0x73, 0x6b, 0x53, 0x76, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x19, 0x67, - 0x65, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, - 0x73, 0x2d, 0x73, 0x76, 0x63, 0x2f, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x54, 0x58, 0xaa, 0x02, - 0x14, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, - 0x76, 0x63, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x14, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x5c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x76, 0x63, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x5c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x76, 0x63, - 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x16, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3a, 0x3a, 0x54, 0x61, 0x73, 0x6b, - 0x73, 0x53, 0x76, 0x63, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6c, 0x0a, 0x0d, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x2b, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, + 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x74, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x78, 0x0a, 0x11, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x2f, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, + 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x73, + 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x30, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x44, 0x75, 0x65, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, + 0x6b, 0x12, 0x28, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, + 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x76, 0x63, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xb6, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, + 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x76, 0x63, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x19, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2d, 0x73, 0x76, 0x63, 0x2f, 0x76, + 0x31, 0xa2, 0x02, 0x03, 0x53, 0x54, 0x58, 0xaa, 0x02, 0x14, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x76, 0x63, 0x2e, 0x56, 0x31, 0xca, 0x02, + 0x14, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x5c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, + 0x76, 0x63, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x5c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x76, 0x63, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x16, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x3a, 0x3a, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x76, 0x63, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3616,7 +3605,6 @@ func file_services_tasks_svc_v1_task_svc_proto_init() { file_services_tasks_svc_v1_task_svc_proto_msgTypes[5].OneofWrappers = []interface{}{} file_services_tasks_svc_v1_task_svc_proto_msgTypes[12].OneofWrappers = []interface{}{} file_services_tasks_svc_v1_task_svc_proto_msgTypes[13].OneofWrappers = []interface{}{} - file_services_tasks_svc_v1_task_svc_proto_msgTypes[14].OneofWrappers = []interface{}{} file_services_tasks_svc_v1_task_svc_proto_msgTypes[15].OneofWrappers = []interface{}{} file_services_tasks_svc_v1_task_svc_proto_msgTypes[18].OneofWrappers = []interface{}{} file_services_tasks_svc_v1_task_svc_proto_msgTypes[19].OneofWrappers = []interface{}{} diff --git a/gen/ts/services/tasks_svc/v1/task_svc_pb.d.ts b/gen/ts/services/tasks_svc/v1/task_svc_pb.d.ts index 1bc57835c..5a09543cd 100644 --- a/gen/ts/services/tasks_svc/v1/task_svc_pb.d.ts +++ b/gen/ts/services/tasks_svc/v1/task_svc_pb.d.ts @@ -951,11 +951,6 @@ export class UnassignTaskRequest extends jspb.Message { getUserId(): string; setUserId(value: string): UnassignTaskRequest; - getConsistency(): string; - setConsistency(value: string): UnassignTaskRequest; - hasConsistency(): boolean; - clearConsistency(): UnassignTaskRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UnassignTaskRequest.AsObject; static toObject(includeInstance: boolean, msg: UnassignTaskRequest): UnassignTaskRequest.AsObject; @@ -968,12 +963,6 @@ export namespace UnassignTaskRequest { export type AsObject = { taskId: string, userId: string, - consistency?: string, - } - - export enum ConsistencyCase { - _CONSISTENCY_NOT_SET = 0, - CONSISTENCY = 3, } } diff --git a/gen/ts/services/tasks_svc/v1/task_svc_pb.js b/gen/ts/services/tasks_svc/v1/task_svc_pb.js index 1e19b6154..255ecdcd6 100644 --- a/gen/ts/services/tasks_svc/v1/task_svc_pb.js +++ b/gen/ts/services/tasks_svc/v1/task_svc_pb.js @@ -7219,8 +7219,7 @@ proto.services.tasks_svc.v1.UnassignTaskRequest.prototype.toObject = function(op proto.services.tasks_svc.v1.UnassignTaskRequest.toObject = function(includeInstance, msg) { var f, obj = { taskId: jspb.Message.getFieldWithDefault(msg, 1, ""), - userId: jspb.Message.getFieldWithDefault(msg, 2, ""), - consistency: jspb.Message.getFieldWithDefault(msg, 3, "") + userId: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -7265,10 +7264,6 @@ proto.services.tasks_svc.v1.UnassignTaskRequest.deserializeBinaryFromReader = fu var value = /** @type {string} */ (reader.readString()); msg.setUserId(value); break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setConsistency(value); - break; default: reader.skipField(); break; @@ -7312,13 +7307,6 @@ proto.services.tasks_svc.v1.UnassignTaskRequest.serializeBinaryToWriter = functi f ); } - f = /** @type {string} */ (jspb.Message.getField(message, 3)); - if (f != null) { - writer.writeString( - 3, - f - ); - } }; @@ -7358,42 +7346,6 @@ proto.services.tasks_svc.v1.UnassignTaskRequest.prototype.setUserId = function(v }; -/** - * optional string consistency = 3; - * @return {string} - */ -proto.services.tasks_svc.v1.UnassignTaskRequest.prototype.getConsistency = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - - -/** - * @param {string} value - * @return {!proto.services.tasks_svc.v1.UnassignTaskRequest} returns this - */ -proto.services.tasks_svc.v1.UnassignTaskRequest.prototype.setConsistency = function(value) { - return jspb.Message.setField(this, 3, value); -}; - - -/** - * Clears the field making it undefined. - * @return {!proto.services.tasks_svc.v1.UnassignTaskRequest} returns this - */ -proto.services.tasks_svc.v1.UnassignTaskRequest.prototype.clearConsistency = function() { - return jspb.Message.setField(this, 3, undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.tasks_svc.v1.UnassignTaskRequest.prototype.hasConsistency = function() { - return jspb.Message.getField(this, 3) != null; -}; - - diff --git a/proto/services/tasks_svc/v1/task_svc.proto b/proto/services/tasks_svc/v1/task_svc.proto index ffc2f1128..a60a8d9f7 100644 --- a/proto/services/tasks_svc/v1/task_svc.proto +++ b/proto/services/tasks_svc/v1/task_svc.proto @@ -219,7 +219,6 @@ message AssignTaskResponse { message UnassignTaskRequest { string task_id = 1; // @gotags: validate:"uuid4" string user_id = 2; // @gotags: validate:"uuid4" - optional string consistency = 3; // no conflict detection, if not provided } message UnassignTaskResponse { diff --git a/services/tasks-svc/internal/task/aggregate/aggregate.go b/services/tasks-svc/internal/task/aggregate/aggregate.go index 4dc0761c0..d6e4736a5 100644 --- a/services/tasks-svc/internal/task/aggregate/aggregate.go +++ b/services/tasks-svc/internal/task/aggregate/aggregate.go @@ -6,6 +6,7 @@ import ( pb "gen/services/tasks_svc/v1" "github.com/google/uuid" "hwes" + "hwutil" taskEventsV1 "tasks-svc/internal/task/events/v1" "tasks-svc/internal/task/models" "time" @@ -50,6 +51,17 @@ func LoadTaskAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore } task := *taskAggregate.Task // deref copies model + + // also copy pointer values + if task.DueAt != nil { + task.DueAt = hwutil.PtrTo(*task.DueAt) + } + subtasks := make(map[uuid.UUID]models.Subtask) + for key, value := range task.Subtasks { + subtasks[key] = value + } + task.Subtasks = subtasks + snapshot = &task } diff --git a/services/tasks-svc/internal/task/api/grpc.go b/services/tasks-svc/internal/task/api/grpc.go index 66846c895..9717e86c9 100644 --- a/services/tasks-svc/internal/task/api/grpc.go +++ b/services/tasks-svc/internal/task/api/grpc.go @@ -8,7 +8,6 @@ import ( pb "gen/services/tasks_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -17,6 +16,7 @@ import ( "hwes" "hwutil" "tasks-svc/internal/task/handlers" + "tasks-svc/internal/task/models" "tasks-svc/internal/util" "time" ) @@ -203,7 +203,7 @@ func (s *TaskGrpcService) AssignTask(ctx context.Context, req *pb.AssignTaskRequ for i := 0; true; i++ { if i > 10 { - log.Warn().Msg("UpdatePatient: conflict circuit breaker triggered") + zlog.Ctx(ctx).Warn().Msg("UpdatePatient: conflict circuit breaker triggered") return nil, fmt.Errorf("failed conflict resolution") } @@ -525,9 +525,66 @@ func (s *TaskGrpcService) UpdateSubtask(ctx context.Context, req *pb.UpdateSubta return nil, err } - consistency, err := s.handlers.Commands.V1.UpdateSubtask(ctx, taskID, subtaskID, req.Subtask.Name, req.Subtask.Done) - if err != nil { - return nil, err + expConsistency, ok := hwutil.ParseConsistency(req.TaskConsistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "task_consistency") + } + + var consistency uint64 + + for i := 0; true; i++ { + if i > 10 { + zlog.Ctx(ctx).Warn().Msg("UpdateSubtask: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.UpdateSubtask(ctx, taskID, subtaskID, req.Subtask.Name, req.Subtask.Done, expConsistency) + if err != nil { + return nil, err + } + consistency = c + + if conflict == nil { + break + } + + conflicts := make(map[string]*commonpb.AttributeConflict) + + var was models.Subtask + if w, ok := conflict.Was.Subtasks[subtaskID]; ok { + was = w + } else { + return nil, fmt.Errorf("subtask did not exist at expConsistency") + } + + is := conflict.Is.Subtasks[subtaskID] // handler would have failed if non-existent + + // TODO: find a generic approach + nameUpdateRequested := req.Subtask.Name != nil && *req.Subtask.Name != is.Name + nameAlreadyUpdated := was.Name != is.Name + if nameUpdateRequested && nameAlreadyUpdated { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(is.Name), + wrapperspb.String(*req.Subtask.Name), + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + return &pb.UpdateSubtaskResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + TaskConsistency: strconv.FormatUint(conflict.Consistency, 10), + }, nil + } + + // bool done can never cause a problem + // the user expects done = B, and sets it to \neg B + // so either that is the case still, or the update will do nothing anyway + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } return &pb.UpdateSubtaskResponse{ diff --git a/services/tasks-svc/internal/task/commands/v1/assign_task.go b/services/tasks-svc/internal/task/commands/v1/assign_task.go index 4ca07c873..4ccbb0a2d 100644 --- a/services/tasks-svc/internal/task/commands/v1/assign_task.go +++ b/services/tasks-svc/internal/task/commands/v1/assign_task.go @@ -15,17 +15,17 @@ type AssignTaskConflict struct { Is *models.Task } -type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignTaskConflict, error) +type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) func NewAssignTaskCommandHandler(as hwes.AggregateStore) AssignTaskCommandHandler { - return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignTaskConflict, error) { + return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) { task, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err } if expConsistency != nil && *expConsistency != task.GetVersion() { - return 0, &AssignTaskConflict{ + return 0, &UpdateTaskConflict{ Consistency: task.GetVersion(), Was: oldState, Is: task.Task, diff --git a/services/tasks-svc/internal/task/commands/v1/update_subtask.go b/services/tasks-svc/internal/task/commands/v1/update_subtask.go index f2e5a3d82..d5dfcf4ff 100644 --- a/services/tasks-svc/internal/task/commands/v1/update_subtask.go +++ b/services/tasks-svc/internal/task/commands/v1/update_subtask.go @@ -9,32 +9,41 @@ import ( "tasks-svc/internal/task/aggregate" ) -type UpdateSubtaskCommandHandler func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool) (common.ConsistencyToken, error) +type UpdateSubtaskCommandHandler func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) func NewUpdateSubtaskCommandHandler(as hwes.AggregateStore) UpdateSubtaskCommandHandler { - return func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool) (common.ConsistencyToken, error) { - a, err := aggregate.LoadTaskAggregate(ctx, as, taskID) + return func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) { + a, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { - return 0, err + return 0, nil, err } currentSubtask, found := a.Task.Subtasks[subtaskID] if !found { - return 0, fmt.Errorf("subtask with ID: %s not found on Task with ID: %s", subtaskID, taskID) + return 0, nil, fmt.Errorf("subtask with ID: %s not found on Task with ID: %s", subtaskID, taskID) + } + + if expConsistency != nil && *expConsistency != a.GetVersion() { + return 0, &UpdateTaskConflict{ + Consistency: a.GetVersion(), + Was: oldState, + Is: a.Task, + }, err } if name != nil && *name != currentSubtask.Name { if err := a.UpdateSubtaskName(ctx, subtaskID, *name); err != nil { - return 0, err + return 0, nil, err } } if done != nil && *done != currentSubtask.Done { if err := a.UpdateSubtaskDone(ctx, subtaskID, *done); err != nil { - return 0, err + return 0, nil, err } } - return as.Save(ctx, a) + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/tasks-svc/stories/TaskCRUD_test.go b/services/tasks-svc/stories/TaskCRUD_test.go index c1f4efd2f..cf5ac16dd 100644 --- a/services/tasks-svc/stories/TaskCRUD_test.go +++ b/services/tasks-svc/stories/TaskCRUD_test.go @@ -201,9 +201,8 @@ func TestCreateUpdateGetTask(t *testing.T) { // unassignRes, err := taskClient.UnassignTask(ctx, &pb.UnassignTaskRequest{ - TaskId: taskId, - UserId: assignedUser.String(), - Consistency: &task.Consistency, + TaskId: taskId, + UserId: assignedUser.String(), }) assert.NoError(t, err) diff --git a/services/tasks-svc/stories/TaskConflicts_test.go b/services/tasks-svc/stories/TaskConflicts_test.go index e94af5013..31f04addf 100644 --- a/services/tasks-svc/stories/TaskConflicts_test.go +++ b/services/tasks-svc/stories/TaskConflicts_test.go @@ -350,9 +350,8 @@ func TestAssignTaskConflict(t *testing.T) { assert.Nil(t, a.Conflict) } else { a, err := taskClient.UnassignTask(ctx, &pb.UnassignTaskRequest{ - TaskId: id, - UserId: *o.was, - Consistency: &initialConsistency, + TaskId: id, + UserId: *o.was, }) assert.NoError(t, err) assert.Nil(t, a.Conflict) @@ -381,3 +380,89 @@ func TestAssignTaskConflict(t *testing.T) { }) } } + +func TestUpdateSubtaskConflict(t *testing.T) { + ctx := context.Background() + taskClient := taskServiceClient() + + patientId := preparePatient(t, ctx, "") + + A := "A" + B := "B" + C := "C" + + testMatrix := []struct { + was string + is string + want string + expectConflict bool + }{ + {A, B, B, false}, + {A, B, C, true}, + {A, A, C, false}, + } + + for i, o := range testMatrix { + t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) { + // WAS + task, err := taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ + Name: t.Name(), + Description: nil, + PatientId: patientId, + Public: nil, + DueAt: nil, + InitialStatus: nil, + AssignedUserId: nil, + Subtasks: nil, + }) + assert.NoError(t, err) + + subTask, err := taskClient.CreateSubtask(ctx, &pb.CreateSubtaskRequest{ + TaskId: task.Id, + Subtask: &pb.CreateSubtaskRequest_Subtask{ + Name: o.was, + Done: nil, + }, + }) + assert.NoError(t, err) + + taskId := task.Id + id := subTask.SubtaskId + initialConsistency := subTask.TaskConsistency + + // IS + a, err := taskClient.UpdateSubtask(ctx, &pb.UpdateSubtaskRequest{ + TaskId: taskId, + SubtaskId: id, + Subtask: &pb.UpdateSubtaskRequest_Subtask{ + Name: &o.is, + }, + TaskConsistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, a.Conflict) + + // WANT + updateRes, err := taskClient.UpdateSubtask(ctx, &pb.UpdateSubtaskRequest{ + TaskId: taskId, + SubtaskId: id, + Subtask: &pb.UpdateSubtaskRequest_Subtask{ + Name: &o.want, + }, + TaskConsistency: &initialConsistency, + }) + assert.NoError(t, err) + + // EXPECT + assert.Equal(t, o.expectConflict, updateRes.Conflict != nil) + if o.expectConflict { + conflict := updateRes.Conflict.ConflictingAttributes["name"] + assert.NotNil(t, conflict) + + exp := "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.is + "\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + o.want + "\"}}" + assert.Equal(t, exp, strings.Replace(conflict.String(), " ", " ", 1)) + } + }) + } +} From 66e0e51215cd5db48e18590e74ecc1080208a73c Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Mon, 7 Oct 2024 10:04:30 +0200 Subject: [PATCH 12/42] integrate ConsistencyToken type everywhere --- libs/common/consistency.go | 16 +++++++++++++++- libs/hwutil/parse.go | 11 ----------- services/tasks-svc/internal/bed/bed.go | 4 ++-- .../internal/patient/aggregate/aggregate.go | 5 +++-- .../tasks-svc/internal/patient/api/grpc.go | 10 +++++----- .../internal/patient/commands/v1/assign_bed.go | 13 +++++++------ .../patient/commands/v1/unassign_bed.go | 13 +++++++------ .../patient/commands/v1/update_patient.go | 13 +++++++------ services/tasks-svc/internal/room/room.go | 4 ++-- .../internal/task-template/task_template.go | 8 ++++---- .../internal/task/aggregate/aggregate.go | 5 +++-- services/tasks-svc/internal/task/api/grpc.go | 18 +++++++++--------- .../internal/task/commands/v1/assign_task.go | 16 +++++----------- .../task/commands/v1/update_subtask.go | 9 +++++---- .../internal/task/commands/v1/update_task.go | 11 ++++++----- services/tasks-svc/internal/ward/ward.go | 4 ++-- services/tasks-svc/stories/PatientCRUD_test.go | 18 +++++++++--------- services/tasks-svc/stories/WardCRUD_test.go | 2 +- 18 files changed, 92 insertions(+), 88 deletions(-) diff --git a/libs/common/consistency.go b/libs/common/consistency.go index dd312ec8d..6da1ebed7 100644 --- a/libs/common/consistency.go +++ b/libs/common/consistency.go @@ -1,9 +1,23 @@ package common -import "strconv" +import ( + "hwutil" + "strconv" +) type ConsistencyToken uint64 func (c ConsistencyToken) String() string { return strconv.FormatUint(uint64(c), 10) } + +func ParseConsistency(consistencyStr *string) (consistency *ConsistencyToken, success bool) { + if consistencyStr == nil { + return nil, true + } + if c, err := strconv.ParseUint(*consistencyStr, 10, 64); err != nil { + return nil, false + } else { + return hwutil.PtrTo(ConsistencyToken(c)), true + } +} diff --git a/libs/hwutil/parse.go b/libs/hwutil/parse.go index f1d56c6e1..952ab5400 100644 --- a/libs/hwutil/parse.go +++ b/libs/hwutil/parse.go @@ -131,14 +131,3 @@ type JSONAble interface { type MapAble interface { ToMap() map[string]interface{} } - -func ParseConsistency(consistencyStr *string) (consistency *uint64, success bool) { - if consistencyStr == nil { - return nil, true - } - if c, err := strconv.ParseUint(*consistencyStr, 10, 64); err != nil { - return nil, false - } else { - return &c, true - } -} diff --git a/services/tasks-svc/internal/bed/bed.go b/services/tasks-svc/internal/bed/bed.go index 25e542cf6..671a2f094 100644 --- a/services/tasks-svc/internal/bed/bed.go +++ b/services/tasks-svc/internal/bed/bed.go @@ -204,7 +204,7 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* return nil, status.Error(codes.InvalidArgument, err.Error()) } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } @@ -229,7 +229,7 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* } // conflict detection - if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + if expConsistency != nil && *expConsistency != common.ConsistencyToken(result.OldConsistency) { // bed is not event sourced yet and has more than one field, // thus we are not able to pinpoint conflicts to a field, we only know *some* update has happened since // for convenience we are filtering out obvious non-conflicts, where the update is the same as the is state, or the field was not changed diff --git a/services/tasks-svc/internal/patient/aggregate/aggregate.go b/services/tasks-svc/internal/patient/aggregate/aggregate.go index 374a814b1..db83d071d 100644 --- a/services/tasks-svc/internal/patient/aggregate/aggregate.go +++ b/services/tasks-svc/internal/patient/aggregate/aggregate.go @@ -1,6 +1,7 @@ package aggregate import ( + "common" "context" "github.com/google/uuid" "hwes" @@ -31,13 +32,13 @@ func LoadPatientAggregate(ctx context.Context, as hwes.AggregateStore, id uuid.U return patientAggregate, nil } -func LoadPatientAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *uint64) (*PatientAggregate, *models.Patient, error) { +func LoadPatientAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *common.ConsistencyToken) (*PatientAggregate, *models.Patient, error) { patientAggregate := NewPatientAggregate(id) var snapshot *models.Patient if pauseAt != nil { // load pauseAt+1-many events (version is 0-indexed) - if err := as.LoadN(ctx, patientAggregate, *pauseAt+1); err != nil { + if err := as.LoadN(ctx, patientAggregate, uint64(*pauseAt)+1); err != nil { return nil, nil, err } diff --git a/services/tasks-svc/internal/patient/api/grpc.go b/services/tasks-svc/internal/patient/api/grpc.go index 6148f3071..652788ece 100644 --- a/services/tasks-svc/internal/patient/api/grpc.go +++ b/services/tasks-svc/internal/patient/api/grpc.go @@ -389,7 +389,7 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa return nil, err } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } @@ -472,7 +472,7 @@ func (s *PatientGrpcService) AssignBed(ctx context.Context, req *pb.AssignBedReq return nil, err } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } @@ -544,12 +544,12 @@ func (s *PatientGrpcService) UnassignBed(ctx context.Context, req *pb.UnassignBe return nil, err } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } - var consistency uint64 + var consistency common.ConsistencyToken for i := 0; true; i++ { if i > 10 { @@ -581,7 +581,7 @@ func (s *PatientGrpcService) UnassignBed(ctx context.Context, req *pb.UnassignBe if len(conflicts) != 0 { return &pb.UnassignBedResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: strconv.FormatUint(conflict.Consistency, 10), + Consistency: conflict.Consistency.String(), }, nil } diff --git a/services/tasks-svc/internal/patient/commands/v1/assign_bed.go b/services/tasks-svc/internal/patient/commands/v1/assign_bed.go index a27957ca9..7b1a47480 100644 --- a/services/tasks-svc/internal/patient/commands/v1/assign_bed.go +++ b/services/tasks-svc/internal/patient/commands/v1/assign_bed.go @@ -10,24 +10,25 @@ import ( ) type AssignBedConflict struct { - Consistency uint64 + Consistency common.ConsistencyToken Was *models.Patient Is *models.Patient } -type AssignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignBedConflict, error) +type AssignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AssignBedConflict, error) func NewAssignBedCommandHandler(as hwes.AggregateStore) AssignBedCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *AssignBedConflict, error) { + return func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AssignBedConflict, error) { a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expConsistency) if err != nil { return 0, nil, err } - // update happened since - if expConsistency != nil && *expConsistency != a.GetVersion() { + // update happened since? + newToken := common.ConsistencyToken(a.GetVersion()) + if expConsistency != nil && *expConsistency != newToken { return 0, &AssignBedConflict{ - Consistency: a.GetVersion(), + Consistency: newToken, Was: oldState, Is: a.Patient, }, nil diff --git a/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go b/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go index 33d5ce076..f8c8af482 100644 --- a/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go +++ b/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go @@ -10,24 +10,25 @@ import ( ) type UnassignBedConflict struct { - Consistency uint64 + Consistency common.ConsistencyToken Was *models.Patient Is *models.Patient } -type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, expectedConsistency *uint64) (common.ConsistencyToken, *UnassignBedConflict, error) +type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, expectedConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UnassignBedConflict, error) func NewUnassignBedCommandHandler(as hwes.AggregateStore) UnassignBedCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, expectedConsistency *uint64) (common.ConsistencyToken, *UnassignBedConflict, error) { + return func(ctx context.Context, patientID uuid.UUID, expectedConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UnassignBedConflict, error) { a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expectedConsistency) if err != nil { return 0, nil, err } - // update has happened since - if expectedConsistency != nil && *expectedConsistency != a.GetVersion() { + // update has happened since? + newToken := common.ConsistencyToken(a.GetVersion()) + if expectedConsistency != nil && *expectedConsistency != newToken { return 0, &UnassignBedConflict{ - Consistency: a.GetVersion(), + Consistency: newToken, Was: oldState, Is: a.Patient, }, nil diff --git a/services/tasks-svc/internal/patient/commands/v1/update_patient.go b/services/tasks-svc/internal/patient/commands/v1/update_patient.go index b216f2bf1..7972efffe 100644 --- a/services/tasks-svc/internal/patient/commands/v1/update_patient.go +++ b/services/tasks-svc/internal/patient/commands/v1/update_patient.go @@ -10,24 +10,25 @@ import ( ) type UpdatePatientConflict struct { - Consistency uint64 + Consistency common.ConsistencyToken Was *models.Patient Is *models.Patient } -type UpdatePatientCommandHandler func(ctx context.Context, patientID uuid.UUID, expConsistency *uint64, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) +type UpdatePatientCommandHandler func(ctx context.Context, patientID uuid.UUID, expConsistency *common.ConsistencyToken, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) func NewUpdatePatientCommandHandler(as hwes.AggregateStore) UpdatePatientCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, expConsistency *uint64, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) { + return func(ctx context.Context, patientID uuid.UUID, expConsistency *common.ConsistencyToken, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) { a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expConsistency) if err != nil { return 0, nil, err } - // an update was performed since expConsistency - if expConsistency != nil && *expConsistency != a.GetVersion() { + // was an update performed since expConsistency? + newToken := common.ConsistencyToken(a.GetVersion()) + if expConsistency != nil && *expConsistency != newToken { return 0, &UpdatePatientConflict{ - Consistency: a.GetVersion(), + Consistency: newToken, Was: oldState, Is: a.Patient, }, nil diff --git a/services/tasks-svc/internal/room/room.go b/services/tasks-svc/internal/room/room.go index dd64b7d54..acff97bd4 100644 --- a/services/tasks-svc/internal/room/room.go +++ b/services/tasks-svc/internal/room/room.go @@ -111,7 +111,7 @@ func (ServiceServer) UpdateRoom(ctx context.Context, req *pb.UpdateRoomRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } @@ -134,7 +134,7 @@ func (ServiceServer) UpdateRoom(ctx context.Context, req *pb.UpdateRoomRequest) } // conflict detection - if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + if expConsistency != nil && *expConsistency != common.ConsistencyToken(result.OldConsistency) { conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { diff --git a/services/tasks-svc/internal/task-template/task_template.go b/services/tasks-svc/internal/task-template/task_template.go index 41581c12c..ed595e99a 100644 --- a/services/tasks-svc/internal/task-template/task_template.go +++ b/services/tasks-svc/internal/task-template/task_template.go @@ -174,7 +174,7 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT return nil, status.Error(codes.InvalidArgument, err.Error()) } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } @@ -199,7 +199,7 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT } // conflict detection - if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + if expConsistency != nil && *expConsistency != common.ConsistencyToken(result.OldConsistency) { // task_template is not event sourced yet and has more than one field, // thus we are not able to pinpoint conflicts to a field, we only know *some* update has happened since // for convenience we are filtering out obvious non-conflicts, where the update is the same as the is state, or the field was not changed @@ -255,7 +255,7 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.UpdateTaskTemplateSubTaskRequest) (*pb.UpdateTaskTemplateSubTaskResponse, error) { - expConsistency, ok := hwutil.ParseConsistency(req.TaskTemplateConsistency) + expConsistency, ok := common.ParseConsistency(req.TaskTemplateConsistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "task_template_consistency") } @@ -294,7 +294,7 @@ func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.Upda } // conflict detection - if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + if expConsistency != nil && *expConsistency != common.ConsistencyToken(result.OldConsistency) { conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != subTaskResult.OldName { diff --git a/services/tasks-svc/internal/task/aggregate/aggregate.go b/services/tasks-svc/internal/task/aggregate/aggregate.go index d6e4736a5..9d2590e6d 100644 --- a/services/tasks-svc/internal/task/aggregate/aggregate.go +++ b/services/tasks-svc/internal/task/aggregate/aggregate.go @@ -1,6 +1,7 @@ package aggregate import ( + "common" "context" "fmt" pb "gen/services/tasks_svc/v1" @@ -40,13 +41,13 @@ func LoadTaskAggregate(ctx context.Context, as hwes.AggregateStore, id uuid.UUID return taskAggregate, nil } -func LoadTaskAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *uint64) (*TaskAggregate, *models.Task, error) { +func LoadTaskAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *common.ConsistencyToken) (*TaskAggregate, *models.Task, error) { taskAggregate := NewTaskAggregate(id) var snapshot *models.Task if pauseAt != nil { // load pauseAt+1-many events (version is 0-indexed) - if err := as.LoadN(ctx, taskAggregate, *pauseAt+1); err != nil { + if err := as.LoadN(ctx, taskAggregate, uint64(*pauseAt)+1); err != nil { return nil, nil, err } diff --git a/services/tasks-svc/internal/task/api/grpc.go b/services/tasks-svc/internal/task/api/grpc.go index 9717e86c9..ab04c0579 100644 --- a/services/tasks-svc/internal/task/api/grpc.go +++ b/services/tasks-svc/internal/task/api/grpc.go @@ -84,12 +84,12 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ return nil, err } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } - var consistency uint64 + var consistency common.ConsistencyToken for i := 0; true; i++ { if i > 10 { @@ -170,7 +170,7 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ if len(conflicts) != 0 { return &pb.UpdateTaskResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: strconv.FormatUint(conflict.Consistency, 10), + Consistency: conflict.Consistency.String(), }, nil } @@ -194,12 +194,12 @@ func (s *TaskGrpcService) AssignTask(ctx context.Context, req *pb.AssignTaskRequ return nil, err } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } - var consistency uint64 + var consistency common.ConsistencyToken for i := 0; true; i++ { if i > 10 { @@ -237,7 +237,7 @@ func (s *TaskGrpcService) AssignTask(ctx context.Context, req *pb.AssignTaskRequ if len(conflicts) != 0 { return &pb.AssignTaskResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: strconv.FormatUint(conflict.Consistency, 10), + Consistency: conflict.Consistency.String(), }, nil } @@ -525,12 +525,12 @@ func (s *TaskGrpcService) UpdateSubtask(ctx context.Context, req *pb.UpdateSubta return nil, err } - expConsistency, ok := hwutil.ParseConsistency(req.TaskConsistency) + expConsistency, ok := common.ParseConsistency(req.TaskConsistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "task_consistency") } - var consistency uint64 + var consistency common.ConsistencyToken for i := 0; true; i++ { if i > 10 { @@ -575,7 +575,7 @@ func (s *TaskGrpcService) UpdateSubtask(ctx context.Context, req *pb.UpdateSubta if len(conflicts) != 0 { return &pb.UpdateSubtaskResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - TaskConsistency: strconv.FormatUint(conflict.Consistency, 10), + TaskConsistency: conflict.Consistency.String(), }, nil } diff --git a/services/tasks-svc/internal/task/commands/v1/assign_task.go b/services/tasks-svc/internal/task/commands/v1/assign_task.go index 4ccbb0a2d..c1a777ee2 100644 --- a/services/tasks-svc/internal/task/commands/v1/assign_task.go +++ b/services/tasks-svc/internal/task/commands/v1/assign_task.go @@ -6,27 +6,21 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/task/aggregate" - "tasks-svc/internal/task/models" ) -type AssignTaskConflict struct { - Consistency uint64 - Was *models.Task - Is *models.Task -} - -type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) +type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) func NewAssignTaskCommandHandler(as hwes.AggregateStore) AssignTaskCommandHandler { - return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) { + return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) { task, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err } - if expConsistency != nil && *expConsistency != task.GetVersion() { + newToken := common.ConsistencyToken(task.GetVersion()) + if expConsistency != nil && *expConsistency != newToken { return 0, &UpdateTaskConflict{ - Consistency: task.GetVersion(), + Consistency: newToken, Was: oldState, Is: task.Task, }, nil diff --git a/services/tasks-svc/internal/task/commands/v1/update_subtask.go b/services/tasks-svc/internal/task/commands/v1/update_subtask.go index d5dfcf4ff..8c1035737 100644 --- a/services/tasks-svc/internal/task/commands/v1/update_subtask.go +++ b/services/tasks-svc/internal/task/commands/v1/update_subtask.go @@ -9,10 +9,10 @@ import ( "tasks-svc/internal/task/aggregate" ) -type UpdateSubtaskCommandHandler func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) +type UpdateSubtaskCommandHandler func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) func NewUpdateSubtaskCommandHandler(as hwes.AggregateStore) UpdateSubtaskCommandHandler { - return func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *uint64) (common.ConsistencyToken, *UpdateTaskConflict, error) { + return func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) { a, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err @@ -23,9 +23,10 @@ func NewUpdateSubtaskCommandHandler(as hwes.AggregateStore) UpdateSubtaskCommand return 0, nil, fmt.Errorf("subtask with ID: %s not found on Task with ID: %s", subtaskID, taskID) } - if expConsistency != nil && *expConsistency != a.GetVersion() { + newToken := common.ConsistencyToken(a.GetVersion()) + if expConsistency != nil && *expConsistency != newToken { return 0, &UpdateTaskConflict{ - Consistency: a.GetVersion(), + Consistency: newToken, Was: oldState, Is: a.Task, }, err diff --git a/services/tasks-svc/internal/task/commands/v1/update_task.go b/services/tasks-svc/internal/task/commands/v1/update_task.go index e9eecf28c..169bfb2eb 100644 --- a/services/tasks-svc/internal/task/commands/v1/update_task.go +++ b/services/tasks-svc/internal/task/commands/v1/update_task.go @@ -12,7 +12,7 @@ import ( ) type UpdateTaskConflict struct { - Consistency uint64 + Consistency common.ConsistencyToken Was *models.Task Is *models.Task } @@ -25,7 +25,7 @@ type UpdateTaskCommandHandler func( status *pb.TaskStatus, public *bool, dueAt *timestamppb.Timestamp, - expConsistency *uint64, + expConsistency *common.ConsistencyToken, ) (common.ConsistencyToken, *UpdateTaskConflict, error) func NewUpdateTaskCommandHandler(as hwes.AggregateStore) UpdateTaskCommandHandler { @@ -36,16 +36,17 @@ func NewUpdateTaskCommandHandler(as hwes.AggregateStore) UpdateTaskCommandHandle status *pb.TaskStatus, public *bool, dueAt *timestamppb.Timestamp, - expConsistency *uint64, + expConsistency *common.ConsistencyToken, ) (common.ConsistencyToken, *UpdateTaskConflict, error) { a, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err } - if expConsistency != nil && *expConsistency != a.GetVersion() { + newToken := common.ConsistencyToken(a.GetVersion()) + if expConsistency != nil && *expConsistency != newToken { return 0, &UpdateTaskConflict{ - Consistency: a.GetVersion(), + Consistency: newToken, Was: oldState, Is: a.Task, }, nil diff --git a/services/tasks-svc/internal/ward/ward.go b/services/tasks-svc/internal/ward/ward.go index 320e42043..3a575c6be 100644 --- a/services/tasks-svc/internal/ward/ward.go +++ b/services/tasks-svc/internal/ward/ward.go @@ -150,7 +150,7 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - expConsistency, ok := hwutil.ParseConsistency(req.Consistency) + expConsistency, ok := common.ParseConsistency(req.Consistency) if !ok { return nil, common.UnparsableConsistencyError(ctx, "consistency") } @@ -174,7 +174,7 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) } // conflict detection - if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) { + if expConsistency != nil && *expConsistency != common.ConsistencyToken(result.OldConsistency) { conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { diff --git a/services/tasks-svc/stories/PatientCRUD_test.go b/services/tasks-svc/stories/PatientCRUD_test.go index 3e92953f8..ac12f9d62 100644 --- a/services/tasks-svc/stories/PatientCRUD_test.go +++ b/services/tasks-svc/stories/PatientCRUD_test.go @@ -91,7 +91,7 @@ func TestCreateUpdateGetPatient(t *testing.T) { readmitRes, err := patientClient.ReadmitPatient(ctx, &pb.ReadmitPatientRequest{PatientId: patientId}) assert.NoError(t, err) assert.NotEqual(t, getPatientRes.Consistency, readmitRes.Consistency) - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // // get re-admitted patient @@ -716,7 +716,7 @@ func TestUpdatePatientConflict(t *testing.T) { id := patientRes.Id initialConsistency := patientRes.Consistency - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // IS _, err = patientClient.UpdatePatient(ctx, &pb.UpdatePatientRequest{ @@ -726,7 +726,7 @@ func TestUpdatePatientConflict(t *testing.T) { Consistency: &initialConsistency, }) assert.NoError(t, err) - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // WANT updateRes, err := patientClient.UpdatePatient(ctx, &pb.UpdatePatientRequest{ @@ -781,7 +781,7 @@ func TestAssignBedConflict(t *testing.T) { Notes: hwutil.PtrTo("A patient for test " + t.Name()), }) assert.NoError(t, err) - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() initialAssignment, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ Id: patientRes.Id, @@ -794,7 +794,7 @@ func TestAssignBedConflict(t *testing.T) { id := patientRes.Id initialConsistency := initialAssignment.Consistency - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // IS if o.is != nil { @@ -813,7 +813,7 @@ func TestAssignBedConflict(t *testing.T) { assert.NoError(t, err) assert.Nil(t, u.Conflict) } - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // WANT updateRes, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ @@ -868,7 +868,7 @@ func TestUnassignBedConflict(t *testing.T) { Notes: hwutil.PtrTo("A patient for test " + t.Name()), }) assert.NoError(t, err) - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() initialAssignment, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{ Id: patientRes.Id, @@ -881,7 +881,7 @@ func TestUnassignBedConflict(t *testing.T) { id := patientRes.Id initialConsistency := initialAssignment.Consistency - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // IS if o.is != nil { @@ -900,7 +900,7 @@ func TestUnassignBedConflict(t *testing.T) { assert.NoError(t, err) assert.Nil(t, u.Conflict) } - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() // WANT updateRes, err := patientClient.UnassignBed(ctx, &pb.UnassignBedRequest{ diff --git a/services/tasks-svc/stories/WardCRUD_test.go b/services/tasks-svc/stories/WardCRUD_test.go index 5ee2ddd7d..d39b7477e 100644 --- a/services/tasks-svc/stories/WardCRUD_test.go +++ b/services/tasks-svc/stories/WardCRUD_test.go @@ -110,7 +110,7 @@ func TestGetRecentWards(t *testing.T) { Id: patientID, BedId: bedId, }) - time.Sleep(time.Millisecond * 100) + hwtesting.WaitForProjectionsToSettle() assert.NoError(t, err, "could not assign bed to patient") _, err = taskClient.CreateTask(ctx, &pb.CreateTaskRequest{ From 30f0c7878731426883b4f5db0475a704b2ec7df6 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Mon, 7 Oct 2024 15:46:02 +0200 Subject: [PATCH 13/42] untested --- .../internal/property/aggregate/aggregate.go | 39 +++- .../internal/property/api/grpc.go | 199 ++++++++++++++++-- .../property/commands/v1/update_property.go | 45 ++-- services/property-svc/util/conflict.go | 35 +++ 4 files changed, 287 insertions(+), 31 deletions(-) create mode 100644 services/property-svc/util/conflict.go diff --git a/services/property-svc/internal/property/aggregate/aggregate.go b/services/property-svc/internal/property/aggregate/aggregate.go index 83ec1de30..91b9d3f66 100644 --- a/services/property-svc/internal/property/aggregate/aggregate.go +++ b/services/property-svc/internal/property/aggregate/aggregate.go @@ -1,6 +1,7 @@ package aggregate import ( + "common" "context" "fmt" pb "gen/services/property_svc/v1" @@ -31,12 +32,44 @@ func NewPropertyAggregate(id uuid.UUID) *PropertyAggregate { return aggregate } -func LoadPropertyAggregate(ctx context.Context, as hwes.AggregateStore, id uuid.UUID) (*PropertyAggregate, error) { +func LoadPropertyAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *common.ConsistencyToken) (*PropertyAggregate, *models.Property, error) { property := NewPropertyAggregate(id) + + var snapshot *models.Property + + if pauseAt != nil { + // load pauseAt+1-many events (version is 0-indexed) + if err := as.LoadN(ctx, property, uint64(*pauseAt)+1); err != nil { + return nil, nil, err + } + + propertyCopy := *property.Property // deref copies model + + // copy pointer values + propertyCopy.FieldTypeData = models.FieldTypeData{} + + sd := property.Property.FieldTypeData.SelectData + if sd != nil { + propertyCopy.FieldTypeData.SelectData = &models.SelectData{ + AllowFreetext: sd.AllowFreetext, + SelectOptions: make([]models.SelectOption, 0), + } + + for _, option := range sd.SelectOptions { + if option.Description != nil { + option.Description = hwutil.PtrTo(*option.Description) + } + propertyCopy.FieldTypeData.SelectData.SelectOptions = append(propertyCopy.FieldTypeData.SelectData.SelectOptions, option) + } + } + snapshot = &propertyCopy + } + + // continue loading all other events if err := as.Load(ctx, property); err != nil { - return nil, err + return nil, nil, err } - return property, nil + return property, snapshot, nil } func (a *PropertyAggregate) initEventListeners() { diff --git a/services/property-svc/internal/property/api/grpc.go b/services/property-svc/internal/property/api/grpc.go index 70e82c233..988da9196 100644 --- a/services/property-svc/internal/property/api/grpc.go +++ b/services/property-svc/internal/property/api/grpc.go @@ -1,13 +1,23 @@ package api import ( + "common" "context" + "fmt" + commonpb "gen/libs/common/v1" pb "gen/services/property_svc/v1" "github.com/google/uuid" + "github.com/rs/zerolog/log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/wrapperspb" "hwes" "hwutil" "property-svc/internal/property/handlers" "property-svc/internal/property/models" + "property-svc/util" + "slices" ) type PropertyGrpcService struct { @@ -126,6 +136,11 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update return nil, err } + expConsistency, ok := common.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + var allowFreetext *bool var removeOptions []string var upsertOptions *[]models.UpdateSelectOption @@ -158,20 +173,176 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update upsertOptions = &opt } } - consistency, err := s.handlers.Commands.V1.UpdateProperty( - ctx, - propertyID, - req.SubjectType, - req.Name, - req.Description, - req.SetId, - allowFreetext, - upsertOptions, - removeOptions, - req.IsArchived, - ) - if err != nil { - return nil, err + + var consistency common.ConsistencyToken + + for i := 0; true; i++ { + if i > 10 { + log.Warn().Msg("UpdatePatient: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.UpdateProperty( + ctx, + propertyID, + req.SubjectType, + req.Name, + req.Description, + req.SetId, + allowFreetext, + upsertOptions, + removeOptions, + req.IsArchived, + expConsistency, + ) + if err != nil { + return nil, err + } + consistency = c + if conflict == nil { + break + } + + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + subjTypeUpdateRequested := req.SubjectType != nil && *req.SubjectType != conflict.Is.SubjectType + subjTypeAlreadyUpdated := conflict.Was.SubjectType != conflict.Is.SubjectType + if subjTypeUpdateRequested && subjTypeAlreadyUpdated { + conflicts["subject_type"], err = util.AttributeConflict( + wrapperspb.Int32(int32(conflict.Is.SubjectType.Number())), + wrapperspb.Int32(int32(req.SubjectType.Number())), + ) + if err != nil { + return nil, err + } + } + + nameUpdateRequested := req.Name != nil && *req.Name != conflict.Is.Name + nameAlreadyUpdated := conflict.Was.Name != conflict.Is.Name + if nameUpdateRequested && nameAlreadyUpdated { + conflicts["name"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.Name), + wrapperspb.String(*req.Name), + ) + if err != nil { + return nil, err + } + } + descrUpdateRequested := req.Description != nil && *req.Description != conflict.Is.Description + descrAlreadyUpdated := conflict.Was.Description != conflict.Is.Description + if descrUpdateRequested && descrAlreadyUpdated { + conflicts["description"], err = util.AttributeConflict( + wrapperspb.String(conflict.Is.Description), + wrapperspb.String(*req.Description), + ) + if err != nil { + return nil, err + } + } + setIdUpdateRequested := req.SetId != nil && *req.SetId != conflict.Is.SetID.UUID.String() + setIdAlreadyUpdated := conflict.Was.SetID != conflict.Is.SetID + if setIdUpdateRequested && setIdAlreadyUpdated { + var is proto.Message + if conflict.Is.SetID.Valid { + is = wrapperspb.String(conflict.Is.SetID.UUID.String()) + } + + conflicts["set_id"], err = util.AttributeConflict( + is, + wrapperspb.String(*req.SetId), + ) + if err != nil { + return nil, err + } + } + + fTDUpdateRequested := req.FieldTypeData != nil && req.GetSelectData() != nil // currently only select data is possible + if fTDUpdateRequested { + sd := req.GetSelectData() + + // boolean can not cause conflicts + + // removal can not cause conflicts + + // upsert can only cause conflicts if update (not insert) + + // nothing to check in the first place + if conflict.Is.FieldTypeData.SelectData == nil || len(conflict.Is.FieldTypeData.SelectData.SelectOptions) == 0 { + continue + } + + for _, upsertedOption := range sd.UpsertOptions { + if upsertedOption.Id == "" { + continue + } + + optionsWas := conflict.Was.FieldTypeData.SelectData.SelectOptions + optionsIs := conflict.Is.FieldTypeData.SelectData.SelectOptions + + searchFn := func(option models.SelectOption) bool { + return option.ID.String() == upsertedOption.Id + } + + // upsert is update, check if it was updated between WAS and IS + wasIx := slices.IndexFunc(optionsWas, searchFn) + if wasIx < 0 { + msg := "The option '%s' you attempted to update did not exist" + + " at the state the provided consistency points to. " + + "You likely provided an incorrect consistency token." + return nil, status.Errorf(codes.InvalidArgument, msg, upsertedOption.Id) + } + wasOpt := optionsWas[wasIx] + + isIx := slices.IndexFunc(optionsIs, searchFn) + if isIx < 0 { + // option was removed since, update will be ignored or cause an error, but will not cause a conflict + continue + } + isOpt := optionsIs[isIx] + + optNameUpdateRequested := upsertedOption.Name != nil && *upsertedOption.Name != isOpt.Name + optnameAlreadyUpdated := wasOpt.Name != isOpt.Name + if optNameUpdateRequested && optnameAlreadyUpdated { + conflicts["select_data.upsert_options."+upsertedOption.Id+".name"], err = util.AttributeConflict( + wrapperspb.String(isOpt.Name), + wrapperspb.String(*upsertedOption.Name), + ) + if err != nil { + return nil, err + } + } + optDescUpdateRequested := upsertedOption.Description != nil && (isOpt.Description == nil || *upsertedOption.Description != *isOpt.Description) + + optDescAlreadyUpdated := func() bool { + if wasOpt.Description != nil && isOpt.Description != nil { + return *wasOpt.Description != *isOpt.Description + } + + return !(wasOpt.Description == nil && isOpt.Description == nil) + } + + if optDescUpdateRequested && optDescAlreadyUpdated() { + conflicts["select_data.upsert_options."+upsertedOption.Id+".description"], err = util.AttributeConflict( + wrapperspb.String(*isOpt.Description), + wrapperspb.String(*upsertedOption.Description), + ) + if err != nil { + return nil, err + } + } + } + } + + if len(conflicts) != 0 { + return &pb.UpdatePropertyResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: conflict.Consistency.String(), + }, nil + } + + // no conflict? retry with new consistency + expConsistency = &conflict.Consistency } return &pb.UpdatePropertyResponse{ diff --git a/services/property-svc/internal/property/commands/v1/update_property.go b/services/property-svc/internal/property/commands/v1/update_property.go index 78c0a95de..e8193b3de 100644 --- a/services/property-svc/internal/property/commands/v1/update_property.go +++ b/services/property-svc/internal/property/commands/v1/update_property.go @@ -10,43 +10,59 @@ import ( "property-svc/internal/property/models" ) -type UpdatePropertyCommandHandler func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool) (common.ConsistencyToken, error) +type UpdatePropertyConflict struct { + Consistency common.ConsistencyToken + Was *models.Property + Is *models.Property +} + +type UpdatePropertyCommandHandler func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdatePropertyConflict, error) func NewUpdatePropertyCommandHandler(as hwes.AggregateStore) UpdatePropertyCommandHandler { - return func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool) (common.ConsistencyToken, error) { - a, err := aggregate.LoadPropertyAggregate(ctx, as, propertyID) + return func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdatePropertyConflict, error) { + a, oldState, err := aggregate.LoadPropertyAggregateWithSnapshotAt(ctx, as, propertyID, expConsistency) if err != nil { - return 0, err + return 0, nil, err + } + + // update happened since? + newToken := common.ConsistencyToken(a.GetVersion()) + if expConsistency != nil && *expConsistency != newToken { + return 0, &UpdatePropertyConflict{ + Consistency: newToken, + Was: oldState, + Is: a.Property, + }, nil } if subjectType != nil { if err := a.UpdateSubjectType(ctx, *subjectType); err != nil { - return 0, err + return 0, nil, err } } if name != nil { if err := a.UpdateName(ctx, *name); err != nil { - return 0, err + return 0, nil, err } } if description != nil { if err := a.UpdateDescription(ctx, *description); err != nil { - return 0, err + return 0, nil, err } } if setID != nil { if err := a.UpdateSetID(ctx, *setID); err != nil { - return 0, err + return 0, nil, err } } if allowFreetext != nil { if a.Property.FieldType == pb.FieldType_FIELD_TYPE_SELECT || a.Property.FieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT { if err := a.UpdateAllowFreetext(ctx, *allowFreetext); err != nil { - return 0, err + return 0, nil, err } } } @@ -54,7 +70,7 @@ func NewUpdatePropertyCommandHandler(as hwes.AggregateStore) UpdatePropertyComma if upsertOptions != nil { if a.Property.FieldType == pb.FieldType_FIELD_TYPE_SELECT || a.Property.FieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT { if err := a.FieldTypeDataUpsertOptions(ctx, *upsertOptions); err != nil { - return 0, err + return 0, nil, err } } @@ -64,7 +80,7 @@ func NewUpdatePropertyCommandHandler(as hwes.AggregateStore) UpdatePropertyComma // TODO: check if remove options exist in aggregate SelectOptions? if a.Property.FieldType == pb.FieldType_FIELD_TYPE_SELECT || a.Property.FieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT { if err := a.FieldTypeDataRemoveOptions(ctx, removeOptions); err != nil { - return 0, err + return 0, nil, err } } } @@ -72,15 +88,16 @@ func NewUpdatePropertyCommandHandler(as hwes.AggregateStore) UpdatePropertyComma if isArchived != nil { if *isArchived { if err := a.ArchiveProperty(ctx); err != nil { - return 0, err + return 0, nil, err } } else { if err := a.RetrieveProperty(ctx); err != nil { - return 0, err + return 0, nil, err } } } - return as.Save(ctx, a) + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/property-svc/util/conflict.go b/services/property-svc/util/conflict.go new file mode 100644 index 000000000..8b429b766 --- /dev/null +++ b/services/property-svc/util/conflict.go @@ -0,0 +1,35 @@ +package util + +import ( + "fmt" + commonpb "gen/libs/common/v1" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +// AttributeConflict is a constructor for commonpb.AttributeConflicts +// I'd love to move this somewhere else, but I also don't want common to depend on gen (and thus hwdb, hwes, ...) +func AttributeConflict(is, want proto.Message) (*commonpb.AttributeConflict, error) { + var err error + + var wantAny *anypb.Any + if want != nil { + wantAny, err = anypb.New(want) + if err != nil { + return nil, fmt.Errorf("AttributeConflict could not marshal want: %w", err) + } + } + + var isAny *anypb.Any + if is != nil { + isAny, err = anypb.New(is) + if err != nil { + return nil, fmt.Errorf("AttributeConflict could not marshal is: %w", err) + } + } + + return &commonpb.AttributeConflict{ + Is: isAny, + Want: wantAny, + }, nil +} From c7916dd7db8e4b3b78a82d05a5f3b996fe23c041 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Tue, 8 Oct 2024 10:56:10 +0200 Subject: [PATCH 14/42] test harness --- .../stories/UpdateProperty_test.go | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 services/property-svc/stories/UpdateProperty_test.go diff --git a/services/property-svc/stories/UpdateProperty_test.go b/services/property-svc/stories/UpdateProperty_test.go new file mode 100644 index 000000000..ac76704aa --- /dev/null +++ b/services/property-svc/stories/UpdateProperty_test.go @@ -0,0 +1,95 @@ +package stories + +import ( + "context" + pb "gen/services/property_svc/v1" + "github.com/stretchr/testify/assert" + "hwtesting" + "hwutil" + "testing" +) + +func TestUpdatePropertyConflict(t *testing.T) { + propertyClient := propertyServiceClient() + + ctx := context.Background() + + // Preparations + + createPropertyRequest := &pb.CreatePropertyRequest{ + SubjectType: pb.SubjectType_SUBJECT_TYPE_TASK, + FieldType: pb.FieldType_FIELD_TYPE_SELECT, + Name: t.Name() + " WAS", + Description: nil, + SetId: nil, + FieldTypeData: &pb.CreatePropertyRequest_SelectData_{SelectData: &pb.CreatePropertyRequest_SelectData{ + Options: []*pb.CreatePropertyRequest_SelectData_SelectOption{ + { + Name: "Option WAS", + Description: hwutil.PtrTo("Option WAS Descr"), + }, + }, + }}, + } + + createRes, err := propertyClient.CreateProperty(ctx, createPropertyRequest) + assert.NoError(t, err) + hwtesting.WaitForProjectionsToSettle() + + propertyID := createRes.PropertyId + initialConsistency := createRes.Consistency + + getRes, err := propertyClient.GetProperty(ctx, &pb.GetPropertyRequest{ + Id: propertyID, + }) + assert.NoError(t, err) + + initialOpts := getRes.GetSelectData().Options + assert.Len(t, initialOpts, 1) + optionID := initialOpts[0].Id + + // First Update + is, err := propertyClient.UpdateProperty(ctx, &pb.UpdatePropertyRequest{ + Id: propertyID, + SubjectType: hwutil.PtrTo(pb.SubjectType_SUBJECT_TYPE_PATIENT), + Name: hwutil.PtrTo(t.Name() + " IS"), + Description: hwutil.PtrTo(t.Name() + " Description IS"), + FieldTypeData: &pb.UpdatePropertyRequest_SelectData_{SelectData: &pb.UpdatePropertyRequest_SelectData{ + UpsertOptions: []*pb.UpdatePropertyRequest_SelectData_SelectOption{ + { + Id: optionID, + Name: hwutil.PtrTo("Option IS"), + Description: hwutil.PtrTo("Option IS Descr"), + }, + }, + }}, + Consistency: &initialConsistency, + }) + + assert.NoError(t, err) + assert.Nil(t, is.Conflict) + hwtesting.WaitForProjectionsToSettle() + + // Second Update + want, err := propertyClient.UpdateProperty(ctx, &pb.UpdatePropertyRequest{ + Id: propertyID, + SubjectType: hwutil.PtrTo(pb.SubjectType_SUBJECT_TYPE_TASK), + Name: hwutil.PtrTo(t.Name() + " WANT"), + Description: hwutil.PtrTo(t.Name() + " Description WANT"), + FieldTypeData: &pb.UpdatePropertyRequest_SelectData_{SelectData: &pb.UpdatePropertyRequest_SelectData{ + UpsertOptions: []*pb.UpdatePropertyRequest_SelectData_SelectOption{ + { + Id: optionID, + Name: hwutil.PtrTo("Option WANT"), + Description: hwutil.PtrTo("Option WANT Descr"), + }, + }, + }}, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + + // Assertions + assert.NotNil(t, want.Conflict) + // TODO +} From 559bfac666ee9f3600bb14f7c8d4fbb09bd5c198 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 10 Oct 2024 08:55:41 +0200 Subject: [PATCH 15/42] add UpdatePropertyConflict test --- libs/hwutil/maps.go | 15 +++++++ libs/hwutil/maps_test.go | 18 ++++++++ ...test.go => UpdatePropertyConflict_test.go} | 41 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 libs/hwutil/maps.go create mode 100644 libs/hwutil/maps_test.go rename services/property-svc/stories/{UpdateProperty_test.go => UpdatePropertyConflict_test.go} (61%) diff --git a/libs/hwutil/maps.go b/libs/hwutil/maps.go new file mode 100644 index 000000000..37cd69728 --- /dev/null +++ b/libs/hwutil/maps.go @@ -0,0 +1,15 @@ +package hwutil + +func Keys[K comparable, V any](mymap map[K]V) []K { + // stolen from https://stackoverflow.com/a/27848197 + + keys := make([]K, len(mymap)) + + i := 0 + for k := range mymap { + keys[i] = k + i++ + } + + return keys +} diff --git a/libs/hwutil/maps_test.go b/libs/hwutil/maps_test.go new file mode 100644 index 000000000..760192eac --- /dev/null +++ b/libs/hwutil/maps_test.go @@ -0,0 +1,18 @@ +package hwutil + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestKeys(t *testing.T) { + in := map[string]string{ + "A": "A", + "B": "A", + "C": "B", + } + out := []string{"A", "B", "C"} + + assert.Subset(t, out, Keys(in)) + assert.Len(t, Keys(in), len(out)) +} diff --git a/services/property-svc/stories/UpdateProperty_test.go b/services/property-svc/stories/UpdatePropertyConflict_test.go similarity index 61% rename from services/property-svc/stories/UpdateProperty_test.go rename to services/property-svc/stories/UpdatePropertyConflict_test.go index ac76704aa..bc190c18a 100644 --- a/services/property-svc/stories/UpdateProperty_test.go +++ b/services/property-svc/stories/UpdatePropertyConflict_test.go @@ -6,6 +6,8 @@ import ( "github.com/stretchr/testify/assert" "hwtesting" "hwutil" + "strconv" + "strings" "testing" ) @@ -91,5 +93,44 @@ func TestUpdatePropertyConflict(t *testing.T) { // Assertions assert.NotNil(t, want.Conflict) + assert.False(t, want.Conflict.HistoryMissing) + + keys := hwutil.Keys(want.Conflict.ConflictingAttributes) + expKeys := []string{ + "name", + "description", + "subject_type", + "select_data.upsert_options." + optionID + ".name", + "select_data.upsert_options." + optionID + ".description", + } + + expValues := []interface{}{ + // name + "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + t.Name() + " IS\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + t.Name() + " WANT\"}}", + // description + "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + t.Name() + " Description IS\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + t.Name() + " Description WANT\"}}", + // subject_type + "is:{[type.googleapis.com/google.protobuf.Int32Value]:{value:" + strconv.Itoa(int(pb.SubjectType_SUBJECT_TYPE_PATIENT.Number())) + "}} " + + "want:{[type.googleapis.com/google.protobuf.Int32Value]:{value:" + strconv.Itoa(int(pb.SubjectType_SUBJECT_TYPE_TASK.Number())) + "}}", + // .name + "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"Option IS\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"Option WANT\"}}", + // .description + "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"Option IS Descr\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"Option WANT Descr\"}}", + } + + assert.Len(t, keys, len(expKeys)) + assert.Subset(t, expKeys, keys) + + for i, key := range expKeys { + assert.Equal(t, + expValues[i], + strings.ReplaceAll(want.Conflict.ConflictingAttributes[key].String(), " ", " "), + ) + } + // TODO } From 08f76d504d4651d8e349342c57c540ba7b6113f6 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 10 Oct 2024 09:02:43 +0200 Subject: [PATCH 16/42] update docs --- gen/dart/lib/libs/common/v1/conflict.pb.dart | 8 ++++++-- gen/go/libs/common/v1/conflict.pb.go | 4 ++++ proto/libs/common/v1/conflict.proto | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gen/dart/lib/libs/common/v1/conflict.pb.dart b/gen/dart/lib/libs/common/v1/conflict.pb.dart index e45c367a4..55bb4fc4d 100644 --- a/gen/dart/lib/libs/common/v1/conflict.pb.dart +++ b/gen/dart/lib/libs/common/v1/conflict.pb.dart @@ -67,8 +67,12 @@ class Conflict extends $pb.GeneratedMessage { static Conflict getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Conflict? _defaultInstance; - /// when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. - /// it is then on you to compare these against your view of the world + /// when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. + /// it is then on you to compare these against your view of the world + /// + /// The key is the json-name of the field you tried to change, subkeys are split by dots ('.'), array elements are represented by the resources id: + /// e.g.: for the request: `{"description": "Conflict", "select_data": {"upsert_options": [{"id": "123", "name": "Conflict"}]}}` + /// this might be the conflict: `{"conflicting_attributes": {"description": ..., "select_data.upsert_options.123.name": ...}}` @$pb.TagNumber(1) $core.Map<$core.String, AttributeConflict> get conflictingAttributes => $_getMap(0); diff --git a/gen/go/libs/common/v1/conflict.pb.go b/gen/go/libs/common/v1/conflict.pb.go index c2831d86f..35e986075 100644 --- a/gen/go/libs/common/v1/conflict.pb.go +++ b/gen/go/libs/common/v1/conflict.pb.go @@ -36,6 +36,10 @@ type Conflict struct { // when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. // it is then on you to compare these against your view of the world + // + // The key is the json-name of the field you tried to change, subkeys are split by dots ('.'), array elements are represented by the resources id: + // e.g.: for the request: `{"description": "Conflict", "select_data": {"upsert_options": [{"id": "123", "name": "Conflict"}]}}` + // this might be the conflict: `{"conflicting_attributes": {"description": ..., "select_data.upsert_options.123.name": ...}}` ConflictingAttributes map[string]*AttributeConflict `protobuf:"bytes,1,rep,name=conflicting_attributes,json=conflictingAttributes,proto3" json:"conflicting_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` HistoryMissing bool `protobuf:"varint,2,opt,name=history_missing,json=historyMissing,proto3" json:"history_missing,omitempty"` } diff --git a/proto/libs/common/v1/conflict.proto b/proto/libs/common/v1/conflict.proto index a5d5ca0d2..d643eacd2 100644 --- a/proto/libs/common/v1/conflict.proto +++ b/proto/libs/common/v1/conflict.proto @@ -16,6 +16,10 @@ option go_package = "gen/libs/common/v1"; message Conflict { // when history_missing is true, this map will contain elements, that might not have been updated since you have seen them last. // it is then on you to compare these against your view of the world + // + // The key is the json-name of the field you tried to change, subkeys are split by dots ('.'), array elements are represented by the resources id: + // e.g.: for the request: `{"description": "Conflict", "select_data": {"upsert_options": [{"id": "123", "name": "Conflict"}]}}` + // this might be the conflict: `{"conflicting_attributes": {"description": ..., "select_data.upsert_options.123.name": ...}}` map conflicting_attributes = 1; bool history_missing = 2; } From 97c5f837f164c0b4a33a1c4130648310b49187d2 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 10 Oct 2024 14:06:54 +0200 Subject: [PATCH 17/42] only want and tests left + (proto fixes) --- gen/dart/lib/libs/common/v1/conflict.pb.dart | 48 + .../lib/libs/common/v1/conflict.pbjson.dart | 13 + .../v1/property_value_svc.pb.dart | 264 +- .../v1/property_value_svc.pbjson.dart | 72 +- gen/go/libs/common/v1/conflict.pb.go | 117 +- .../property_svc/v1/property_value_svc.pb.go | 715 +++-- gen/ts/libs/common/v1/conflict_pb.d.ts | 20 + gen/ts/libs/common/v1/conflict_pb.js | 182 ++ .../v1/property_value_svc_pb.d.ts | 106 +- .../property_svc/v1/property_value_svc_pb.js | 2748 ++++++++--------- proto/libs/common/v1/conflict.proto | 8 + .../property_svc/v1/property_value_svc.proto | 18 +- .../internal/property-value/api/grpc.go | 30 +- .../commands/v1/attach_property_value.go | 101 +- .../repos/property_value_repo.sql | 16 +- .../property_value_repo.sql.go | 62 +- 16 files changed, 2508 insertions(+), 2012 deletions(-) diff --git a/gen/dart/lib/libs/common/v1/conflict.pb.dart b/gen/dart/lib/libs/common/v1/conflict.pb.dart index 55bb4fc4d..0c08440a6 100644 --- a/gen/dart/lib/libs/common/v1/conflict.pb.dart +++ b/gen/dart/lib/libs/common/v1/conflict.pb.dart @@ -133,6 +133,7 @@ class AttributeConflict extends $pb.GeneratedMessage { /// CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) /// Enums are returned as Int32s + /// Arrays are encoded as AnyArrays @$pb.TagNumber(1) $19.Any get is_1 => $_getN(0); @$pb.TagNumber(1) @@ -146,6 +147,7 @@ class AttributeConflict extends $pb.GeneratedMessage { /// CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) /// Enums are returned as Int32s + /// Arrays are encoded as AnyArrays @$pb.TagNumber(2) $19.Any get want => $_getN(1); @$pb.TagNumber(2) @@ -158,6 +160,52 @@ class AttributeConflict extends $pb.GeneratedMessage { $19.Any ensureWant() => $_ensure(1); } +/// there is no native Any-compatible wrapper for arrays, +/// so here is one +class AnyArray extends $pb.GeneratedMessage { + factory AnyArray({ + $core.Iterable<$19.Any>? elements, + }) { + final $result = create(); + if (elements != null) { + $result.elements.addAll(elements); + } + return $result; + } + AnyArray._() : super(); + factory AnyArray.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory AnyArray.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AnyArray', package: const $pb.PackageName(_omitMessageNames ? '' : 'libs.common.v1'), createEmptyInstance: create) + ..pc<$19.Any>(1, _omitFieldNames ? '' : 'elements', $pb.PbFieldType.PM, subBuilder: $19.Any.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + AnyArray clone() => AnyArray()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + AnyArray copyWith(void Function(AnyArray) updates) => super.copyWith((message) => updates(message as AnyArray)) as AnyArray; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AnyArray create() => AnyArray._(); + AnyArray createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static AnyArray getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static AnyArray? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$19.Any> get elements => $_getList(0); +} + const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/gen/dart/lib/libs/common/v1/conflict.pbjson.dart b/gen/dart/lib/libs/common/v1/conflict.pbjson.dart index b3490a427..e49a4e5ff 100644 --- a/gen/dart/lib/libs/common/v1/conflict.pbjson.dart +++ b/gen/dart/lib/libs/common/v1/conflict.pbjson.dart @@ -55,3 +55,16 @@ final $typed_data.Uint8List attributeConflictDescriptor = $convert.base64Decode( 'ChFBdHRyaWJ1dGVDb25mbGljdBIkCgJpcxgBIAEoCzIULmdvb2dsZS5wcm90b2J1Zi5BbnlSAm' 'lzEigKBHdhbnQYAiABKAsyFC5nb29nbGUucHJvdG9idWYuQW55UgR3YW50'); +@$core.Deprecated('Use anyArrayDescriptor instead') +const AnyArray$json = { + '1': 'AnyArray', + '2': [ + {'1': 'elements', '3': 1, '4': 3, '5': 11, '6': '.google.protobuf.Any', '10': 'elements'}, + ], +}; + +/// Descriptor for `AnyArray`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List anyArrayDescriptor = $convert.base64Decode( + 'CghBbnlBcnJheRIwCghlbGVtZW50cxgBIAMoCzIULmdvb2dsZS5wcm90b2J1Zi5BbnlSCGVsZW' + '1lbnRz'); + diff --git a/gen/dart/lib/services/property_svc/v1/property_value_svc.pb.dart b/gen/dart/lib/services/property_svc/v1/property_value_svc.pb.dart index f993fdce7..682152f96 100644 --- a/gen/dart/lib/services/property_svc/v1/property_value_svc.pb.dart +++ b/gen/dart/lib/services/property_svc/v1/property_value_svc.pb.dart @@ -18,6 +18,128 @@ import '../../../libs/common/v1/conflict.pb.dart' as $21; import 'types.pb.dart' as $22; import 'types.pbenum.dart' as $22; +class SelectValueOption extends $pb.GeneratedMessage { + factory SelectValueOption({ + $core.String? id, + $core.String? name, + $core.String? description, + }) { + final $result = create(); + if (id != null) { + $result.id = id; + } + if (name != null) { + $result.name = name; + } + if (description != null) { + $result.description = description; + } + return $result; + } + SelectValueOption._() : super(); + factory SelectValueOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SelectValueOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SelectValueOption', package: const $pb.PackageName(_omitMessageNames ? '' : 'services.property_svc.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'name') + ..aOS(3, _omitFieldNames ? '' : 'description') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + SelectValueOption clone() => SelectValueOption()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + SelectValueOption copyWith(void Function(SelectValueOption) updates) => super.copyWith((message) => updates(message as SelectValueOption)) as SelectValueOption; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SelectValueOption create() => SelectValueOption._(); + SelectValueOption createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static SelectValueOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static SelectValueOption? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => clearField(1); + + @$pb.TagNumber(2) + $core.String get name => $_getSZ(1); + @$pb.TagNumber(2) + set name($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasName() => $_has(1); + @$pb.TagNumber(2) + void clearName() => clearField(2); + + @$pb.TagNumber(3) + $core.String get description => $_getSZ(2); + @$pb.TagNumber(3) + set description($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasDescription() => $_has(2); + @$pb.TagNumber(3) + void clearDescription() => clearField(3); +} + +class MultiSelectValue extends $pb.GeneratedMessage { + factory MultiSelectValue({ + $core.Iterable? selectValues, + }) { + final $result = create(); + if (selectValues != null) { + $result.selectValues.addAll(selectValues); + } + return $result; + } + MultiSelectValue._() : super(); + factory MultiSelectValue.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory MultiSelectValue.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'MultiSelectValue', package: const $pb.PackageName(_omitMessageNames ? '' : 'services.property_svc.v1'), createEmptyInstance: create) + ..pc(1, _omitFieldNames ? '' : 'selectValues', $pb.PbFieldType.PM, subBuilder: SelectValueOption.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + MultiSelectValue clone() => MultiSelectValue()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + MultiSelectValue copyWith(void Function(MultiSelectValue) updates) => super.copyWith((message) => updates(message as MultiSelectValue)) as MultiSelectValue; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static MultiSelectValue create() => MultiSelectValue._(); + MultiSelectValue createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static MultiSelectValue getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static MultiSelectValue? _defaultInstance; + + @$pb.TagNumber(1) + $core.List get selectValues => $_getList(0); +} + class AttachPropertyValueRequest_MultiSelectValue extends $pb.GeneratedMessage { factory AttachPropertyValueRequest_MultiSelectValue({ $core.Iterable<$core.String>? selectValues, @@ -571,128 +693,6 @@ class GetAttachedPropertyValuesRequest extends $pb.GeneratedMessage { PatientPropertyMatcher ensurePatientMatcher() => $_ensure(1); } -class GetAttachedPropertyValuesResponse_Value_SelectValueOption extends $pb.GeneratedMessage { - factory GetAttachedPropertyValuesResponse_Value_SelectValueOption({ - $core.String? id, - $core.String? name, - $core.String? description, - }) { - final $result = create(); - if (id != null) { - $result.id = id; - } - if (name != null) { - $result.name = name; - } - if (description != null) { - $result.description = description; - } - return $result; - } - GetAttachedPropertyValuesResponse_Value_SelectValueOption._() : super(); - factory GetAttachedPropertyValuesResponse_Value_SelectValueOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory GetAttachedPropertyValuesResponse_Value_SelectValueOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GetAttachedPropertyValuesResponse.Value.SelectValueOption', package: const $pb.PackageName(_omitMessageNames ? '' : 'services.property_svc.v1'), createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'id') - ..aOS(2, _omitFieldNames ? '' : 'name') - ..aOS(3, _omitFieldNames ? '' : 'description') - ..hasRequiredFields = false - ; - - @$core.Deprecated( - 'Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - GetAttachedPropertyValuesResponse_Value_SelectValueOption clone() => GetAttachedPropertyValuesResponse_Value_SelectValueOption()..mergeFromMessage(this); - @$core.Deprecated( - 'Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - GetAttachedPropertyValuesResponse_Value_SelectValueOption copyWith(void Function(GetAttachedPropertyValuesResponse_Value_SelectValueOption) updates) => super.copyWith((message) => updates(message as GetAttachedPropertyValuesResponse_Value_SelectValueOption)) as GetAttachedPropertyValuesResponse_Value_SelectValueOption; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static GetAttachedPropertyValuesResponse_Value_SelectValueOption create() => GetAttachedPropertyValuesResponse_Value_SelectValueOption._(); - GetAttachedPropertyValuesResponse_Value_SelectValueOption createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static GetAttachedPropertyValuesResponse_Value_SelectValueOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static GetAttachedPropertyValuesResponse_Value_SelectValueOption? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); - @$pb.TagNumber(1) - set id($core.String v) { $_setString(0, v); } - @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); - @$pb.TagNumber(1) - void clearId() => clearField(1); - - @$pb.TagNumber(2) - $core.String get name => $_getSZ(1); - @$pb.TagNumber(2) - set name($core.String v) { $_setString(1, v); } - @$pb.TagNumber(2) - $core.bool hasName() => $_has(1); - @$pb.TagNumber(2) - void clearName() => clearField(2); - - @$pb.TagNumber(3) - $core.String get description => $_getSZ(2); - @$pb.TagNumber(3) - set description($core.String v) { $_setString(2, v); } - @$pb.TagNumber(3) - $core.bool hasDescription() => $_has(2); - @$pb.TagNumber(3) - void clearDescription() => clearField(3); -} - -class GetAttachedPropertyValuesResponse_Value_MultiSelectValue extends $pb.GeneratedMessage { - factory GetAttachedPropertyValuesResponse_Value_MultiSelectValue({ - $core.Iterable? selectValues, - }) { - final $result = create(); - if (selectValues != null) { - $result.selectValues.addAll(selectValues); - } - return $result; - } - GetAttachedPropertyValuesResponse_Value_MultiSelectValue._() : super(); - factory GetAttachedPropertyValuesResponse_Value_MultiSelectValue.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); - factory GetAttachedPropertyValuesResponse_Value_MultiSelectValue.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GetAttachedPropertyValuesResponse.Value.MultiSelectValue', package: const $pb.PackageName(_omitMessageNames ? '' : 'services.property_svc.v1'), createEmptyInstance: create) - ..pc(1, _omitFieldNames ? '' : 'selectValues', $pb.PbFieldType.PM, subBuilder: GetAttachedPropertyValuesResponse_Value_SelectValueOption.create) - ..hasRequiredFields = false - ; - - @$core.Deprecated( - 'Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - GetAttachedPropertyValuesResponse_Value_MultiSelectValue clone() => GetAttachedPropertyValuesResponse_Value_MultiSelectValue()..mergeFromMessage(this); - @$core.Deprecated( - 'Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - GetAttachedPropertyValuesResponse_Value_MultiSelectValue copyWith(void Function(GetAttachedPropertyValuesResponse_Value_MultiSelectValue) updates) => super.copyWith((message) => updates(message as GetAttachedPropertyValuesResponse_Value_MultiSelectValue)) as GetAttachedPropertyValuesResponse_Value_MultiSelectValue; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static GetAttachedPropertyValuesResponse_Value_MultiSelectValue create() => GetAttachedPropertyValuesResponse_Value_MultiSelectValue._(); - GetAttachedPropertyValuesResponse_Value_MultiSelectValue createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static GetAttachedPropertyValuesResponse_Value_MultiSelectValue getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static GetAttachedPropertyValuesResponse_Value_MultiSelectValue? _defaultInstance; - - @$pb.TagNumber(1) - $core.List get selectValues => $_getList(0); -} - enum GetAttachedPropertyValuesResponse_Value_Value { textValue, numberValue, @@ -716,8 +716,8 @@ class GetAttachedPropertyValuesResponse_Value extends $pb.GeneratedMessage { $core.bool? boolValue, $22.Date? dateValue, $20.Timestamp? dateTimeValue, - GetAttachedPropertyValuesResponse_Value_SelectValueOption? selectValue, - GetAttachedPropertyValuesResponse_Value_MultiSelectValue? multiSelectValue, + SelectValueOption? selectValue, + MultiSelectValue? multiSelectValue, $core.String? propertyConsistency, $core.String? valueConsistency, }) { @@ -792,8 +792,8 @@ class GetAttachedPropertyValuesResponse_Value extends $pb.GeneratedMessage { ..aOB(8, _omitFieldNames ? '' : 'boolValue') ..aOM<$22.Date>(9, _omitFieldNames ? '' : 'dateValue', subBuilder: $22.Date.create) ..aOM<$20.Timestamp>(10, _omitFieldNames ? '' : 'dateTimeValue', subBuilder: $20.Timestamp.create) - ..aOM(11, _omitFieldNames ? '' : 'selectValue', subBuilder: GetAttachedPropertyValuesResponse_Value_SelectValueOption.create) - ..aOM(12, _omitFieldNames ? '' : 'multiSelectValue', subBuilder: GetAttachedPropertyValuesResponse_Value_MultiSelectValue.create) + ..aOM(11, _omitFieldNames ? '' : 'selectValue', subBuilder: SelectValueOption.create) + ..aOM(12, _omitFieldNames ? '' : 'multiSelectValue', subBuilder: MultiSelectValue.create) ..aOS(13, _omitFieldNames ? '' : 'propertyConsistency') ..aOS(14, _omitFieldNames ? '' : 'valueConsistency') ..hasRequiredFields = false @@ -918,26 +918,26 @@ class GetAttachedPropertyValuesResponse_Value extends $pb.GeneratedMessage { $20.Timestamp ensureDateTimeValue() => $_ensure(9); @$pb.TagNumber(11) - GetAttachedPropertyValuesResponse_Value_SelectValueOption get selectValue => $_getN(10); + SelectValueOption get selectValue => $_getN(10); @$pb.TagNumber(11) - set selectValue(GetAttachedPropertyValuesResponse_Value_SelectValueOption v) { setField(11, v); } + set selectValue(SelectValueOption v) { setField(11, v); } @$pb.TagNumber(11) $core.bool hasSelectValue() => $_has(10); @$pb.TagNumber(11) void clearSelectValue() => clearField(11); @$pb.TagNumber(11) - GetAttachedPropertyValuesResponse_Value_SelectValueOption ensureSelectValue() => $_ensure(10); + SelectValueOption ensureSelectValue() => $_ensure(10); @$pb.TagNumber(12) - GetAttachedPropertyValuesResponse_Value_MultiSelectValue get multiSelectValue => $_getN(11); + MultiSelectValue get multiSelectValue => $_getN(11); @$pb.TagNumber(12) - set multiSelectValue(GetAttachedPropertyValuesResponse_Value_MultiSelectValue v) { setField(12, v); } + set multiSelectValue(MultiSelectValue v) { setField(12, v); } @$pb.TagNumber(12) $core.bool hasMultiSelectValue() => $_has(11); @$pb.TagNumber(12) void clearMultiSelectValue() => clearField(12); @$pb.TagNumber(12) - GetAttachedPropertyValuesResponse_Value_MultiSelectValue ensureMultiSelectValue() => $_ensure(11); + MultiSelectValue ensureMultiSelectValue() => $_ensure(11); @$pb.TagNumber(13) $core.String get propertyConsistency => $_getSZ(12); diff --git a/gen/dart/lib/services/property_svc/v1/property_value_svc.pbjson.dart b/gen/dart/lib/services/property_svc/v1/property_value_svc.pbjson.dart index c4dc33f2e..ba906d696 100644 --- a/gen/dart/lib/services/property_svc/v1/property_value_svc.pbjson.dart +++ b/gen/dart/lib/services/property_svc/v1/property_value_svc.pbjson.dart @@ -13,6 +13,34 @@ import 'dart:convert' as $convert; import 'dart:core' as $core; import 'dart:typed_data' as $typed_data; +@$core.Deprecated('Use selectValueOptionDescriptor instead') +const SelectValueOption$json = { + '1': 'SelectValueOption', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, + {'1': 'description', '3': 3, '4': 1, '5': 9, '10': 'description'}, + ], +}; + +/// Descriptor for `SelectValueOption`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List selectValueOptionDescriptor = $convert.base64Decode( + 'ChFTZWxlY3RWYWx1ZU9wdGlvbhIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZR' + 'IgCgtkZXNjcmlwdGlvbhgDIAEoCVILZGVzY3JpcHRpb24='); + +@$core.Deprecated('Use multiSelectValueDescriptor instead') +const MultiSelectValue$json = { + '1': 'MultiSelectValue', + '2': [ + {'1': 'select_values', '3': 1, '4': 3, '5': 11, '6': '.services.property_svc.v1.SelectValueOption', '10': 'selectValues'}, + ], +}; + +/// Descriptor for `MultiSelectValue`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List multiSelectValueDescriptor = $convert.base64Decode( + 'ChBNdWx0aVNlbGVjdFZhbHVlElAKDXNlbGVjdF92YWx1ZXMYASADKAsyKy5zZXJ2aWNlcy5wcm' + '9wZXJ0eV9zdmMudjEuU2VsZWN0VmFsdWVPcHRpb25SDHNlbGVjdFZhbHVlcw=='); + @$core.Deprecated('Use attachPropertyValueRequestDescriptor instead') const AttachPropertyValueRequest$json = { '1': 'AttachPropertyValueRequest', @@ -158,12 +186,11 @@ const GetAttachedPropertyValuesResponse_Value$json = { {'1': 'bool_value', '3': 8, '4': 1, '5': 8, '9': 0, '10': 'boolValue'}, {'1': 'date_value', '3': 9, '4': 1, '5': 11, '6': '.services.property_svc.v1.Date', '9': 0, '10': 'dateValue'}, {'1': 'date_time_value', '3': 10, '4': 1, '5': 11, '6': '.google.protobuf.Timestamp', '9': 0, '10': 'dateTimeValue'}, - {'1': 'select_value', '3': 11, '4': 1, '5': 11, '6': '.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption', '9': 0, '10': 'selectValue'}, - {'1': 'multi_select_value', '3': 12, '4': 1, '5': 11, '6': '.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue', '9': 0, '10': 'multiSelectValue'}, + {'1': 'select_value', '3': 11, '4': 1, '5': 11, '6': '.services.property_svc.v1.SelectValueOption', '9': 0, '10': 'selectValue'}, + {'1': 'multi_select_value', '3': 12, '4': 1, '5': 11, '6': '.services.property_svc.v1.MultiSelectValue', '9': 0, '10': 'multiSelectValue'}, {'1': 'property_consistency', '3': 13, '4': 1, '5': 9, '10': 'propertyConsistency'}, {'1': 'value_consistency', '3': 14, '4': 1, '5': 9, '9': 2, '10': 'valueConsistency', '17': true}, ], - '3': [GetAttachedPropertyValuesResponse_Value_SelectValueOption$json, GetAttachedPropertyValuesResponse_Value_MultiSelectValue$json], '8': [ {'1': 'value'}, {'1': '_description'}, @@ -171,29 +198,11 @@ const GetAttachedPropertyValuesResponse_Value$json = { ], }; -@$core.Deprecated('Use getAttachedPropertyValuesResponseDescriptor instead') -const GetAttachedPropertyValuesResponse_Value_SelectValueOption$json = { - '1': 'SelectValueOption', - '2': [ - {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, - {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, - {'1': 'description', '3': 3, '4': 1, '5': 9, '10': 'description'}, - ], -}; - -@$core.Deprecated('Use getAttachedPropertyValuesResponseDescriptor instead') -const GetAttachedPropertyValuesResponse_Value_MultiSelectValue$json = { - '1': 'MultiSelectValue', - '2': [ - {'1': 'select_values', '3': 1, '4': 3, '5': 11, '6': '.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption', '10': 'selectValues'}, - ], -}; - /// Descriptor for `GetAttachedPropertyValuesResponse`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List getAttachedPropertyValuesResponseDescriptor = $convert.base64Decode( 'CiFHZXRBdHRhY2hlZFByb3BlcnR5VmFsdWVzUmVzcG9uc2USWQoGdmFsdWVzGAEgAygLMkEuc2' 'VydmljZXMucHJvcGVydHlfc3ZjLnYxLkdldEF0dGFjaGVkUHJvcGVydHlWYWx1ZXNSZXNwb25z' - 'ZS5WYWx1ZVIGdmFsdWVzGrMICgVWYWx1ZRIfCgtwcm9wZXJ0eV9pZBgBIAEoCVIKcHJvcGVydH' + 'ZS5WYWx1ZVIGdmFsdWVzGvgFCgVWYWx1ZRIfCgtwcm9wZXJ0eV9pZBgBIAEoCVIKcHJvcGVydH' 'lJZBJCCgpmaWVsZF90eXBlGAIgASgOMiMuc2VydmljZXMucHJvcGVydHlfc3ZjLnYxLkZpZWxk' 'VHlwZVIJZmllbGRUeXBlEhIKBG5hbWUYAyABKAlSBG5hbWUSJQoLZGVzY3JpcHRpb24YBCABKA' 'lIAVILZGVzY3JpcHRpb26IAQESHwoLaXNfYXJjaGl2ZWQYBSABKAhSCmlzQXJjaGl2ZWQSHwoK' @@ -201,16 +210,11 @@ final $typed_data.Uint8List getAttachedPropertyValuesResponseDescriptor = $conve '51bWJlclZhbHVlEh8KCmJvb2xfdmFsdWUYCCABKAhIAFIJYm9vbFZhbHVlEj8KCmRhdGVfdmFs' 'dWUYCSABKAsyHi5zZXJ2aWNlcy5wcm9wZXJ0eV9zdmMudjEuRGF0ZUgAUglkYXRlVmFsdWUSRA' 'oPZGF0ZV90aW1lX3ZhbHVlGAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAUg1k' - 'YXRlVGltZVZhbHVlEngKDHNlbGVjdF92YWx1ZRgLIAEoCzJTLnNlcnZpY2VzLnByb3BlcnR5X3' - 'N2Yy52MS5HZXRBdHRhY2hlZFByb3BlcnR5VmFsdWVzUmVzcG9uc2UuVmFsdWUuU2VsZWN0VmFs' - 'dWVPcHRpb25IAFILc2VsZWN0VmFsdWUSggEKEm11bHRpX3NlbGVjdF92YWx1ZRgMIAEoCzJSLn' - 'NlcnZpY2VzLnByb3BlcnR5X3N2Yy52MS5HZXRBdHRhY2hlZFByb3BlcnR5VmFsdWVzUmVzcG9u' - 'c2UuVmFsdWUuTXVsdGlTZWxlY3RWYWx1ZUgAUhBtdWx0aVNlbGVjdFZhbHVlEjEKFHByb3Blcn' - 'R5X2NvbnNpc3RlbmN5GA0gASgJUhNwcm9wZXJ0eUNvbnNpc3RlbmN5EjAKEXZhbHVlX2NvbnNp' - 'c3RlbmN5GA4gASgJSAJSEHZhbHVlQ29uc2lzdGVuY3mIAQEaWQoRU2VsZWN0VmFsdWVPcHRpb2' - '4SDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSIAoLZGVzY3JpcHRpb24YAyAB' - 'KAlSC2Rlc2NyaXB0aW9uGowBChBNdWx0aVNlbGVjdFZhbHVlEngKDXNlbGVjdF92YWx1ZXMYAS' - 'ADKAsyUy5zZXJ2aWNlcy5wcm9wZXJ0eV9zdmMudjEuR2V0QXR0YWNoZWRQcm9wZXJ0eVZhbHVl' - 'c1Jlc3BvbnNlLlZhbHVlLlNlbGVjdFZhbHVlT3B0aW9uUgxzZWxlY3RWYWx1ZXNCBwoFdmFsdW' - 'VCDgoMX2Rlc2NyaXB0aW9uQhQKEl92YWx1ZV9jb25zaXN0ZW5jeQ=='); + 'YXRlVGltZVZhbHVlElAKDHNlbGVjdF92YWx1ZRgLIAEoCzIrLnNlcnZpY2VzLnByb3BlcnR5X3' + 'N2Yy52MS5TZWxlY3RWYWx1ZU9wdGlvbkgAUgtzZWxlY3RWYWx1ZRJaChJtdWx0aV9zZWxlY3Rf' + 'dmFsdWUYDCABKAsyKi5zZXJ2aWNlcy5wcm9wZXJ0eV9zdmMudjEuTXVsdGlTZWxlY3RWYWx1ZU' + 'gAUhBtdWx0aVNlbGVjdFZhbHVlEjEKFHByb3BlcnR5X2NvbnNpc3RlbmN5GA0gASgJUhNwcm9w' + 'ZXJ0eUNvbnNpc3RlbmN5EjAKEXZhbHVlX2NvbnNpc3RlbmN5GA4gASgJSAJSEHZhbHVlQ29uc2' + 'lzdGVuY3mIAQFCBwoFdmFsdWVCDgoMX2Rlc2NyaXB0aW9uQhQKEl92YWx1ZV9jb25zaXN0ZW5j' + 'eQ=='); diff --git a/gen/go/libs/common/v1/conflict.pb.go b/gen/go/libs/common/v1/conflict.pb.go index 35e986075..9d92a7c35 100644 --- a/gen/go/libs/common/v1/conflict.pb.go +++ b/gen/go/libs/common/v1/conflict.pb.go @@ -97,9 +97,11 @@ type AttributeConflict struct { // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) // Enums are returned as Int32s + // Arrays are encoded as AnyArrays Is *anypb.Any `protobuf:"bytes,1,opt,name=is,proto3" json:"is,omitempty"` // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) // Enums are returned as Int32s + // Arrays are encoded as AnyArrays Want *anypb.Any `protobuf:"bytes,2,opt,name=want,proto3" json:"want,omitempty"` } @@ -149,6 +151,55 @@ func (x *AttributeConflict) GetWant() *anypb.Any { return nil } +// there is no native Any-compatible wrapper for arrays, +// so here is one +type AnyArray struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Elements []*anypb.Any `protobuf:"bytes,1,rep,name=elements,proto3" json:"elements,omitempty"` +} + +func (x *AnyArray) Reset() { + *x = AnyArray{} + if protoimpl.UnsafeEnabled { + mi := &file_libs_common_v1_conflict_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AnyArray) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnyArray) ProtoMessage() {} + +func (x *AnyArray) ProtoReflect() protoreflect.Message { + mi := &file_libs_common_v1_conflict_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnyArray.ProtoReflect.Descriptor instead. +func (*AnyArray) Descriptor() ([]byte, []int) { + return file_libs_common_v1_conflict_proto_rawDescGZIP(), []int{2} +} + +func (x *AnyArray) GetElements() []*anypb.Any { + if x != nil { + return x.Elements + } + return nil +} + var File_libs_common_v1_conflict_proto protoreflect.FileDescriptor var file_libs_common_v1_conflict_proto_rawDesc = []byte{ @@ -179,17 +230,21 @@ var file_libs_common_v1_conflict_proto_rawDesc = []byte{ 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x02, 0x69, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x77, 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x77, 0x61, 0x6e, 0x74, 0x42, 0x91, - 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x12, 0x67, 0x65, 0x6e, 0x2f, 0x6c, 0x69, 0x62, 0x73, - 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x4c, 0x43, 0x58, - 0xaa, 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, - 0x31, 0xca, 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, - 0x56, 0x31, 0xe2, 0x02, 0x1a, 0x4c, 0x69, 0x62, 0x73, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x10, 0x4c, 0x69, 0x62, 0x73, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, - 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x04, 0x77, 0x61, 0x6e, 0x74, 0x22, 0x3c, + 0x0a, 0x08, 0x41, 0x6e, 0x79, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x30, 0x0a, 0x08, 0x65, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, + 0x6e, 0x79, 0x52, 0x08, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x91, 0x01, 0x0a, + 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x12, 0x67, 0x65, 0x6e, 0x2f, 0x6c, 0x69, 0x62, 0x73, 0x2f, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x4c, 0x43, 0x58, 0xaa, 0x02, + 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0xca, + 0x02, 0x0e, 0x4c, 0x69, 0x62, 0x73, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, + 0xe2, 0x02, 0x1a, 0x4c, 0x69, 0x62, 0x73, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, + 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, + 0x4c, 0x69, 0x62, 0x73, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -204,23 +259,25 @@ func file_libs_common_v1_conflict_proto_rawDescGZIP() []byte { return file_libs_common_v1_conflict_proto_rawDescData } -var file_libs_common_v1_conflict_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_libs_common_v1_conflict_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_libs_common_v1_conflict_proto_goTypes = []interface{}{ (*Conflict)(nil), // 0: libs.common.v1.Conflict (*AttributeConflict)(nil), // 1: libs.common.v1.AttributeConflict - nil, // 2: libs.common.v1.Conflict.ConflictingAttributesEntry - (*anypb.Any)(nil), // 3: google.protobuf.Any + (*AnyArray)(nil), // 2: libs.common.v1.AnyArray + nil, // 3: libs.common.v1.Conflict.ConflictingAttributesEntry + (*anypb.Any)(nil), // 4: google.protobuf.Any } var file_libs_common_v1_conflict_proto_depIdxs = []int32{ - 2, // 0: libs.common.v1.Conflict.conflicting_attributes:type_name -> libs.common.v1.Conflict.ConflictingAttributesEntry - 3, // 1: libs.common.v1.AttributeConflict.is:type_name -> google.protobuf.Any - 3, // 2: libs.common.v1.AttributeConflict.want:type_name -> google.protobuf.Any - 1, // 3: libs.common.v1.Conflict.ConflictingAttributesEntry.value:type_name -> libs.common.v1.AttributeConflict - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] 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 + 3, // 0: libs.common.v1.Conflict.conflicting_attributes:type_name -> libs.common.v1.Conflict.ConflictingAttributesEntry + 4, // 1: libs.common.v1.AttributeConflict.is:type_name -> google.protobuf.Any + 4, // 2: libs.common.v1.AttributeConflict.want:type_name -> google.protobuf.Any + 4, // 3: libs.common.v1.AnyArray.elements:type_name -> google.protobuf.Any + 1, // 4: libs.common.v1.Conflict.ConflictingAttributesEntry.value:type_name -> libs.common.v1.AttributeConflict + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_libs_common_v1_conflict_proto_init() } @@ -253,6 +310,18 @@ func file_libs_common_v1_conflict_proto_init() { return nil } } + file_libs_common_v1_conflict_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AnyArray); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -260,7 +329,7 @@ func file_libs_common_v1_conflict_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_libs_common_v1_conflict_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/services/property_svc/v1/property_value_svc.pb.go b/gen/go/services/property_svc/v1/property_value_svc.pb.go index 4b0d48a31..c588b03b8 100644 --- a/gen/go/services/property_svc/v1/property_value_svc.pb.go +++ b/gen/go/services/property_svc/v1/property_value_svc.pb.go @@ -22,6 +22,116 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type SelectValueOption struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` +} + +func (x *SelectValueOption) Reset() { + *x = SelectValueOption{} + if protoimpl.UnsafeEnabled { + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SelectValueOption) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SelectValueOption) ProtoMessage() {} + +func (x *SelectValueOption) ProtoReflect() protoreflect.Message { + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SelectValueOption.ProtoReflect.Descriptor instead. +func (*SelectValueOption) Descriptor() ([]byte, []int) { + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{0} +} + +func (x *SelectValueOption) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SelectValueOption) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SelectValueOption) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +type MultiSelectValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SelectValues []*SelectValueOption `protobuf:"bytes,1,rep,name=select_values,json=selectValues,proto3" json:"select_values,omitempty"` +} + +func (x *MultiSelectValue) Reset() { + *x = MultiSelectValue{} + if protoimpl.UnsafeEnabled { + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MultiSelectValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MultiSelectValue) ProtoMessage() {} + +func (x *MultiSelectValue) ProtoReflect() protoreflect.Message { + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MultiSelectValue.ProtoReflect.Descriptor instead. +func (*MultiSelectValue) Descriptor() ([]byte, []int) { + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{1} +} + +func (x *MultiSelectValue) GetSelectValues() []*SelectValueOption { + if x != nil { + return x.SelectValues + } + return nil +} + // AttachPropertyValueRequest type AttachPropertyValueRequest struct { state protoimpl.MessageState @@ -47,7 +157,7 @@ type AttachPropertyValueRequest struct { func (x *AttachPropertyValueRequest) Reset() { *x = AttachPropertyValueRequest{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[0] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -60,7 +170,7 @@ func (x *AttachPropertyValueRequest) String() string { func (*AttachPropertyValueRequest) ProtoMessage() {} func (x *AttachPropertyValueRequest) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[0] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -73,7 +183,7 @@ func (x *AttachPropertyValueRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachPropertyValueRequest.ProtoReflect.Descriptor instead. func (*AttachPropertyValueRequest) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{0} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{2} } func (x *AttachPropertyValueRequest) GetSubjectId() string { @@ -212,7 +322,7 @@ type AttachPropertyValueResponse struct { func (x *AttachPropertyValueResponse) Reset() { *x = AttachPropertyValueResponse{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[1] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -225,7 +335,7 @@ func (x *AttachPropertyValueResponse) String() string { func (*AttachPropertyValueResponse) ProtoMessage() {} func (x *AttachPropertyValueResponse) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[1] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -238,7 +348,7 @@ func (x *AttachPropertyValueResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachPropertyValueResponse.ProtoReflect.Descriptor instead. func (*AttachPropertyValueResponse) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{1} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{3} } func (x *AttachPropertyValueResponse) GetPropertyValueId() string { @@ -274,7 +384,7 @@ type TaskPropertyMatcher struct { func (x *TaskPropertyMatcher) Reset() { *x = TaskPropertyMatcher{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[2] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -287,7 +397,7 @@ func (x *TaskPropertyMatcher) String() string { func (*TaskPropertyMatcher) ProtoMessage() {} func (x *TaskPropertyMatcher) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[2] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -300,7 +410,7 @@ func (x *TaskPropertyMatcher) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskPropertyMatcher.ProtoReflect.Descriptor instead. func (*TaskPropertyMatcher) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{2} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{4} } func (x *TaskPropertyMatcher) GetWardId() string { @@ -329,7 +439,7 @@ type PatientPropertyMatcher struct { func (x *PatientPropertyMatcher) Reset() { *x = PatientPropertyMatcher{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[3] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -342,7 +452,7 @@ func (x *PatientPropertyMatcher) String() string { func (*PatientPropertyMatcher) ProtoMessage() {} func (x *PatientPropertyMatcher) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[3] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -355,7 +465,7 @@ func (x *PatientPropertyMatcher) ProtoReflect() protoreflect.Message { // Deprecated: Use PatientPropertyMatcher.ProtoReflect.Descriptor instead. func (*PatientPropertyMatcher) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{3} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{5} } func (x *PatientPropertyMatcher) GetWardId() string { @@ -387,7 +497,7 @@ type GetAttachedPropertyValuesRequest struct { func (x *GetAttachedPropertyValuesRequest) Reset() { *x = GetAttachedPropertyValuesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[4] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -400,7 +510,7 @@ func (x *GetAttachedPropertyValuesRequest) String() string { func (*GetAttachedPropertyValuesRequest) ProtoMessage() {} func (x *GetAttachedPropertyValuesRequest) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[4] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -413,7 +523,7 @@ func (x *GetAttachedPropertyValuesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAttachedPropertyValuesRequest.ProtoReflect.Descriptor instead. func (*GetAttachedPropertyValuesRequest) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{4} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{6} } func (m *GetAttachedPropertyValuesRequest) GetMatcher() isGetAttachedPropertyValuesRequest_Matcher { @@ -465,7 +575,7 @@ type GetAttachedPropertyValuesResponse struct { func (x *GetAttachedPropertyValuesResponse) Reset() { *x = GetAttachedPropertyValuesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[5] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -478,7 +588,7 @@ func (x *GetAttachedPropertyValuesResponse) String() string { func (*GetAttachedPropertyValuesResponse) ProtoMessage() {} func (x *GetAttachedPropertyValuesResponse) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[5] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -491,7 +601,7 @@ func (x *GetAttachedPropertyValuesResponse) ProtoReflect() protoreflect.Message // Deprecated: Use GetAttachedPropertyValuesResponse.ProtoReflect.Descriptor instead. func (*GetAttachedPropertyValuesResponse) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{5} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{7} } func (x *GetAttachedPropertyValuesResponse) GetValues() []*GetAttachedPropertyValuesResponse_Value { @@ -513,7 +623,7 @@ type AttachPropertyValueRequest_MultiSelectValue struct { func (x *AttachPropertyValueRequest_MultiSelectValue) Reset() { *x = AttachPropertyValueRequest_MultiSelectValue{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[6] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -526,7 +636,7 @@ func (x *AttachPropertyValueRequest_MultiSelectValue) String() string { func (*AttachPropertyValueRequest_MultiSelectValue) ProtoMessage() {} func (x *AttachPropertyValueRequest_MultiSelectValue) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[6] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -539,7 +649,7 @@ func (x *AttachPropertyValueRequest_MultiSelectValue) ProtoReflect() protoreflec // Deprecated: Use AttachPropertyValueRequest_MultiSelectValue.ProtoReflect.Descriptor instead. func (*AttachPropertyValueRequest_MultiSelectValue) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{0, 0} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{2, 0} } func (x *AttachPropertyValueRequest_MultiSelectValue) GetSelectValues() []string { @@ -574,7 +684,7 @@ type GetAttachedPropertyValuesResponse_Value struct { // *GetAttachedPropertyValuesResponse_Value_DateValue // *GetAttachedPropertyValuesResponse_Value_DateTimeValue // *GetAttachedPropertyValuesResponse_Value_SelectValue - // *GetAttachedPropertyValuesResponse_Value_MultiSelectValue_ + // *GetAttachedPropertyValuesResponse_Value_MultiSelectValue Value isGetAttachedPropertyValuesResponse_Value_Value `protobuf_oneof:"value"` PropertyConsistency string `protobuf:"bytes,13,opt,name=property_consistency,json=propertyConsistency,proto3" json:"property_consistency,omitempty"` ValueConsistency *string `protobuf:"bytes,14,opt,name=value_consistency,json=valueConsistency,proto3,oneof" json:"value_consistency,omitempty"` // missing, if no value available @@ -583,7 +693,7 @@ type GetAttachedPropertyValuesResponse_Value struct { func (x *GetAttachedPropertyValuesResponse_Value) Reset() { *x = GetAttachedPropertyValuesResponse_Value{} if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[7] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -596,7 +706,7 @@ func (x *GetAttachedPropertyValuesResponse_Value) String() string { func (*GetAttachedPropertyValuesResponse_Value) ProtoMessage() {} func (x *GetAttachedPropertyValuesResponse_Value) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[7] + mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -609,7 +719,7 @@ func (x *GetAttachedPropertyValuesResponse_Value) ProtoReflect() protoreflect.Me // Deprecated: Use GetAttachedPropertyValuesResponse_Value.ProtoReflect.Descriptor instead. func (*GetAttachedPropertyValuesResponse_Value) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{5, 0} + return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{7, 0} } func (x *GetAttachedPropertyValuesResponse_Value) GetPropertyId() string { @@ -689,15 +799,15 @@ func (x *GetAttachedPropertyValuesResponse_Value) GetDateTimeValue() *timestampp return nil } -func (x *GetAttachedPropertyValuesResponse_Value) GetSelectValue() *GetAttachedPropertyValuesResponse_Value_SelectValueOption { +func (x *GetAttachedPropertyValuesResponse_Value) GetSelectValue() *SelectValueOption { if x, ok := x.GetValue().(*GetAttachedPropertyValuesResponse_Value_SelectValue); ok { return x.SelectValue } return nil } -func (x *GetAttachedPropertyValuesResponse_Value) GetMultiSelectValue() *GetAttachedPropertyValuesResponse_Value_MultiSelectValue { - if x, ok := x.GetValue().(*GetAttachedPropertyValuesResponse_Value_MultiSelectValue_); ok { +func (x *GetAttachedPropertyValuesResponse_Value) GetMultiSelectValue() *MultiSelectValue { + if x, ok := x.GetValue().(*GetAttachedPropertyValuesResponse_Value_MultiSelectValue); ok { return x.MultiSelectValue } return nil @@ -742,11 +852,11 @@ type GetAttachedPropertyValuesResponse_Value_DateTimeValue struct { } type GetAttachedPropertyValuesResponse_Value_SelectValue struct { - SelectValue *GetAttachedPropertyValuesResponse_Value_SelectValueOption `protobuf:"bytes,11,opt,name=select_value,json=selectValue,proto3,oneof"` // FIELD_TYPE_SELECT, id of option + SelectValue *SelectValueOption `protobuf:"bytes,11,opt,name=select_value,json=selectValue,proto3,oneof"` // FIELD_TYPE_SELECT, id of option } -type GetAttachedPropertyValuesResponse_Value_MultiSelectValue_ struct { - MultiSelectValue *GetAttachedPropertyValuesResponse_Value_MultiSelectValue `protobuf:"bytes,12,opt,name=multi_select_value,json=multiSelectValue,proto3,oneof"` +type GetAttachedPropertyValuesResponse_Value_MultiSelectValue struct { + MultiSelectValue *MultiSelectValue `protobuf:"bytes,12,opt,name=multi_select_value,json=multiSelectValue,proto3,oneof"` } func (*GetAttachedPropertyValuesResponse_Value_TextValue) isGetAttachedPropertyValuesResponse_Value_Value() { @@ -767,117 +877,7 @@ func (*GetAttachedPropertyValuesResponse_Value_DateTimeValue) isGetAttachedPrope func (*GetAttachedPropertyValuesResponse_Value_SelectValue) isGetAttachedPropertyValuesResponse_Value_Value() { } -func (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue_) isGetAttachedPropertyValuesResponse_Value_Value() { -} - -type GetAttachedPropertyValuesResponse_Value_SelectValueOption struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` -} - -func (x *GetAttachedPropertyValuesResponse_Value_SelectValueOption) Reset() { - *x = GetAttachedPropertyValuesResponse_Value_SelectValueOption{} - if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAttachedPropertyValuesResponse_Value_SelectValueOption) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAttachedPropertyValuesResponse_Value_SelectValueOption) ProtoMessage() {} - -func (x *GetAttachedPropertyValuesResponse_Value_SelectValueOption) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAttachedPropertyValuesResponse_Value_SelectValueOption.ProtoReflect.Descriptor instead. -func (*GetAttachedPropertyValuesResponse_Value_SelectValueOption) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{5, 0, 0} -} - -func (x *GetAttachedPropertyValuesResponse_Value_SelectValueOption) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *GetAttachedPropertyValuesResponse_Value_SelectValueOption) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *GetAttachedPropertyValuesResponse_Value_SelectValueOption) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -type GetAttachedPropertyValuesResponse_Value_MultiSelectValue struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - SelectValues []*GetAttachedPropertyValuesResponse_Value_SelectValueOption `protobuf:"bytes,1,rep,name=select_values,json=selectValues,proto3" json:"select_values,omitempty"` -} - -func (x *GetAttachedPropertyValuesResponse_Value_MultiSelectValue) Reset() { - *x = GetAttachedPropertyValuesResponse_Value_MultiSelectValue{} - if protoimpl.UnsafeEnabled { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAttachedPropertyValuesResponse_Value_MultiSelectValue) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue) ProtoMessage() {} - -func (x *GetAttachedPropertyValuesResponse_Value_MultiSelectValue) ProtoReflect() protoreflect.Message { - mi := &file_services_property_svc_v1_property_value_svc_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAttachedPropertyValuesResponse_Value_MultiSelectValue.ProtoReflect.Descriptor instead. -func (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue) Descriptor() ([]byte, []int) { - return file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP(), []int{5, 0, 1} -} - -func (x *GetAttachedPropertyValuesResponse_Value_MultiSelectValue) GetSelectValues() []*GetAttachedPropertyValuesResponse_Value_SelectValueOption { - if x != nil { - return x.SelectValues - } - return nil +func (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue) isGetAttachedPropertyValuesResponse_Value_Value() { } var File_services_property_svc_v1_property_value_svc_proto protoreflect.FileDescriptor @@ -894,137 +894,144 @@ var file_services_property_svc_v1_property_value_svc_proto_rawDesc = []byte{ 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x6c, 0x69, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0x91, 0x05, 0x0a, 0x1a, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x50, 0x72, - 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, - 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x74, 0x65, 0x78, 0x74, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, - 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x64, 0x61, 0x74, - 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, - 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, - 0x09, 0x64, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x64, 0x61, - 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, - 0x00, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x75, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x73, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x45, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, + 0x6f, 0x74, 0x6f, 0x22, 0x59, 0x0a, 0x11, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x64, + 0x0a, 0x10, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x50, 0x0a, 0x0d, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, + 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x22, 0x91, 0x05, 0x0a, 0x1a, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x50, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, + 0x79, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x74, 0x65, 0x78, 0x74, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, + 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, + 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x64, 0x61, + 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x09, 0x64, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x64, + 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x48, 0x00, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x75, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, + 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x88, 0x01, 0x01, 0x1a, 0x69, 0x0a, 0x10, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x30, 0x0a, + 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, + 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x63, 0x6f, 0x6e, + 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xb3, 0x01, 0x0a, 0x1b, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x75, 0x6c, 0x74, - 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x25, 0x0a, 0x0b, - 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x01, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x88, 0x01, 0x01, 0x1a, 0x69, 0x0a, 0x10, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x14, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0x07, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x63, 0x6f, 0x6e, 0x73, - 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xb3, 0x01, 0x0a, 0x1b, 0x41, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x70, 0x65, - 0x72, 0x74, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x48, - 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x20, - 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0x69, 0x0a, - 0x13, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4d, 0x61, 0x74, - 0x63, 0x68, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x88, - 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x88, 0x01, 0x01, - 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x42, 0x0a, 0x0a, 0x08, - 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x22, 0x75, 0x0a, 0x16, 0x50, 0x61, 0x74, 0x69, - 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, - 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x88, 0x01, 0x01, - 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x09, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, - 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x22, - 0xde, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, - 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x0c, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6d, 0x61, 0x74, - 0x63, 0x68, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, - 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, - 0x74, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0b, 0x74, 0x61, 0x73, - 0x6b, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x0f, 0x70, 0x61, 0x74, 0x69, - 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x30, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x73, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, + 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, + 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, + 0x79, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x22, 0x69, + 0x0a, 0x13, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x88, 0x01, + 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x42, 0x0a, 0x0a, + 0x08, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x22, 0x75, 0x0a, 0x16, 0x50, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, - 0x22, 0xb4, 0x09, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, + 0x68, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x77, 0x61, 0x72, 0x64, 0x49, 0x64, 0x88, 0x01, + 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x09, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, + 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x22, 0xde, 0x01, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x1a, 0xb3, 0x08, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, - 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x0a, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x23, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x73, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0a, 0x69, 0x73, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0a, - 0x74, 0x65, 0x78, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x09, 0x74, 0x65, 0x78, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, - 0x0c, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x0c, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x6d, 0x61, + 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, + 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x70, 0x65, + 0x72, 0x74, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0b, 0x74, 0x61, + 0x73, 0x6b, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x0f, 0x70, 0x61, 0x74, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, + 0x74, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4d, 0x61, 0x74, + 0x63, 0x68, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x74, 0x69, 0x65, 0x6e, 0x74, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, + 0x72, 0x22, 0xf9, 0x06, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, + 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x61, 0x74, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x78, 0x0a, 0x0c, 0x73, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x53, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x73, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x52, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x1a, 0xf8, 0x05, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x49, 0x64, 0x12, 0x42, 0x0a, + 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x23, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x0b, + 0x69, 0x73, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x69, 0x73, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x12, 0x1f, 0x0a, + 0x0a, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x09, 0x74, 0x65, 0x78, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, + 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x61, 0x74, 0x65, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x0d, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x50, 0x0a, 0x0c, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, + 0x52, 0x0b, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x5a, 0x0a, + 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, + 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x31, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, @@ -1032,58 +1039,44 @@ var file_services_property_svc_v1_property_value_svc_proto_rawDesc = []byte{ 0x79, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x30, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x88, 0x01, 0x01, 0x1a, 0x59, - 0x0a, 0x11, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x8c, 0x01, 0x0a, 0x10, 0x4d, 0x75, - 0x6c, 0x74, 0x69, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x78, - 0x0a, 0x0d, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x53, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, - 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x32, 0xb6, 0x02, 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x84, 0x01, 0x0a, 0x13, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, - 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, - 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, + 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x88, 0x01, 0x01, 0x42, 0x07, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x32, 0xb6, 0x02, + 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x84, 0x01, 0x0a, 0x13, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x34, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, - 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x96, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3a, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x3b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0xd1, 0x01, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, - 0x31, 0x42, 0x15, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x53, 0x76, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1c, 0x67, 0x65, 0x6e, 0x2f, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, - 0x79, 0x2d, 0x73, 0x76, 0x63, 0x2f, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x53, 0x50, 0x58, 0xaa, 0x02, - 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, - 0x74, 0x79, 0x53, 0x76, 0x63, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x96, 0x01, + 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3a, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, + 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, 0x73, 0x76, 0x63, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x50, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xd1, 0x01, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, + 0x5f, 0x73, 0x76, 0x63, 0x2e, 0x76, 0x31, 0x42, 0x15, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x76, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x1c, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x70, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x2d, 0x73, 0x76, 0x63, 0x2f, 0x76, 0x31, 0xa2, 0x02, + 0x03, 0x53, 0x50, 0x58, 0xaa, 0x02, 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x76, 0x63, 0x2e, 0x56, 0x31, 0xca, 0x02, + 0x17, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x5c, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x74, 0x79, 0x53, 0x76, 0x63, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x23, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x5c, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x76, 0x63, 0x5c, - 0x56, 0x31, 0xe2, 0x02, 0x23, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x5c, 0x50, 0x72, - 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x76, 0x63, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x19, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x3a, 0x3a, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x76, 0x63, - 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, + 0x19, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3a, 0x3a, 0x50, 0x72, 0x6f, 0x70, 0x65, + 0x72, 0x74, 0x79, 0x53, 0x76, 0x63, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -1100,39 +1093,39 @@ func file_services_property_svc_v1_property_value_svc_proto_rawDescGZIP() []byte var file_services_property_svc_v1_property_value_svc_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_services_property_svc_v1_property_value_svc_proto_goTypes = []interface{}{ - (*AttachPropertyValueRequest)(nil), // 0: services.property_svc.v1.AttachPropertyValueRequest - (*AttachPropertyValueResponse)(nil), // 1: services.property_svc.v1.AttachPropertyValueResponse - (*TaskPropertyMatcher)(nil), // 2: services.property_svc.v1.TaskPropertyMatcher - (*PatientPropertyMatcher)(nil), // 3: services.property_svc.v1.PatientPropertyMatcher - (*GetAttachedPropertyValuesRequest)(nil), // 4: services.property_svc.v1.GetAttachedPropertyValuesRequest - (*GetAttachedPropertyValuesResponse)(nil), // 5: services.property_svc.v1.GetAttachedPropertyValuesResponse - (*AttachPropertyValueRequest_MultiSelectValue)(nil), // 6: services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue - (*GetAttachedPropertyValuesResponse_Value)(nil), // 7: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value - (*GetAttachedPropertyValuesResponse_Value_SelectValueOption)(nil), // 8: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption - (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue)(nil), // 9: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue + (*SelectValueOption)(nil), // 0: services.property_svc.v1.SelectValueOption + (*MultiSelectValue)(nil), // 1: services.property_svc.v1.MultiSelectValue + (*AttachPropertyValueRequest)(nil), // 2: services.property_svc.v1.AttachPropertyValueRequest + (*AttachPropertyValueResponse)(nil), // 3: services.property_svc.v1.AttachPropertyValueResponse + (*TaskPropertyMatcher)(nil), // 4: services.property_svc.v1.TaskPropertyMatcher + (*PatientPropertyMatcher)(nil), // 5: services.property_svc.v1.PatientPropertyMatcher + (*GetAttachedPropertyValuesRequest)(nil), // 6: services.property_svc.v1.GetAttachedPropertyValuesRequest + (*GetAttachedPropertyValuesResponse)(nil), // 7: services.property_svc.v1.GetAttachedPropertyValuesResponse + (*AttachPropertyValueRequest_MultiSelectValue)(nil), // 8: services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue + (*GetAttachedPropertyValuesResponse_Value)(nil), // 9: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value (*Date)(nil), // 10: services.property_svc.v1.Date (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp (*v1.Conflict)(nil), // 12: libs.common.v1.Conflict (FieldType)(0), // 13: services.property_svc.v1.FieldType } var file_services_property_svc_v1_property_value_svc_proto_depIdxs = []int32{ - 10, // 0: services.property_svc.v1.AttachPropertyValueRequest.date_value:type_name -> services.property_svc.v1.Date - 11, // 1: services.property_svc.v1.AttachPropertyValueRequest.date_time_value:type_name -> google.protobuf.Timestamp - 6, // 2: services.property_svc.v1.AttachPropertyValueRequest.multi_select_value:type_name -> services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue - 12, // 3: services.property_svc.v1.AttachPropertyValueResponse.conflict:type_name -> libs.common.v1.Conflict - 2, // 4: services.property_svc.v1.GetAttachedPropertyValuesRequest.task_matcher:type_name -> services.property_svc.v1.TaskPropertyMatcher - 3, // 5: services.property_svc.v1.GetAttachedPropertyValuesRequest.patient_matcher:type_name -> services.property_svc.v1.PatientPropertyMatcher - 7, // 6: services.property_svc.v1.GetAttachedPropertyValuesResponse.values:type_name -> services.property_svc.v1.GetAttachedPropertyValuesResponse.Value - 13, // 7: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.field_type:type_name -> services.property_svc.v1.FieldType - 10, // 8: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.date_value:type_name -> services.property_svc.v1.Date - 11, // 9: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.date_time_value:type_name -> google.protobuf.Timestamp - 8, // 10: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.select_value:type_name -> services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption - 9, // 11: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.multi_select_value:type_name -> services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue - 8, // 12: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.select_values:type_name -> services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption - 0, // 13: services.property_svc.v1.PropertyValueService.AttachPropertyValue:input_type -> services.property_svc.v1.AttachPropertyValueRequest - 4, // 14: services.property_svc.v1.PropertyValueService.GetAttachedPropertyValues:input_type -> services.property_svc.v1.GetAttachedPropertyValuesRequest - 1, // 15: services.property_svc.v1.PropertyValueService.AttachPropertyValue:output_type -> services.property_svc.v1.AttachPropertyValueResponse - 5, // 16: services.property_svc.v1.PropertyValueService.GetAttachedPropertyValues:output_type -> services.property_svc.v1.GetAttachedPropertyValuesResponse + 0, // 0: services.property_svc.v1.MultiSelectValue.select_values:type_name -> services.property_svc.v1.SelectValueOption + 10, // 1: services.property_svc.v1.AttachPropertyValueRequest.date_value:type_name -> services.property_svc.v1.Date + 11, // 2: services.property_svc.v1.AttachPropertyValueRequest.date_time_value:type_name -> google.protobuf.Timestamp + 8, // 3: services.property_svc.v1.AttachPropertyValueRequest.multi_select_value:type_name -> services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue + 12, // 4: services.property_svc.v1.AttachPropertyValueResponse.conflict:type_name -> libs.common.v1.Conflict + 4, // 5: services.property_svc.v1.GetAttachedPropertyValuesRequest.task_matcher:type_name -> services.property_svc.v1.TaskPropertyMatcher + 5, // 6: services.property_svc.v1.GetAttachedPropertyValuesRequest.patient_matcher:type_name -> services.property_svc.v1.PatientPropertyMatcher + 9, // 7: services.property_svc.v1.GetAttachedPropertyValuesResponse.values:type_name -> services.property_svc.v1.GetAttachedPropertyValuesResponse.Value + 13, // 8: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.field_type:type_name -> services.property_svc.v1.FieldType + 10, // 9: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.date_value:type_name -> services.property_svc.v1.Date + 11, // 10: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.date_time_value:type_name -> google.protobuf.Timestamp + 0, // 11: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.select_value:type_name -> services.property_svc.v1.SelectValueOption + 1, // 12: services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.multi_select_value:type_name -> services.property_svc.v1.MultiSelectValue + 2, // 13: services.property_svc.v1.PropertyValueService.AttachPropertyValue:input_type -> services.property_svc.v1.AttachPropertyValueRequest + 6, // 14: services.property_svc.v1.PropertyValueService.GetAttachedPropertyValues:input_type -> services.property_svc.v1.GetAttachedPropertyValuesRequest + 3, // 15: services.property_svc.v1.PropertyValueService.AttachPropertyValue:output_type -> services.property_svc.v1.AttachPropertyValueResponse + 7, // 16: services.property_svc.v1.PropertyValueService.GetAttachedPropertyValues:output_type -> services.property_svc.v1.GetAttachedPropertyValuesResponse 15, // [15:17] is the sub-list for method output_type 13, // [13:15] is the sub-list for method input_type 13, // [13:13] is the sub-list for extension type_name @@ -1148,7 +1141,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { file_services_property_svc_v1_types_proto_init() if !protoimpl.UnsafeEnabled { file_services_property_svc_v1_property_value_svc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttachPropertyValueRequest); i { + switch v := v.(*SelectValueOption); i { case 0: return &v.state case 1: @@ -1160,7 +1153,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttachPropertyValueResponse); i { + switch v := v.(*MultiSelectValue); i { case 0: return &v.state case 1: @@ -1172,7 +1165,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TaskPropertyMatcher); i { + switch v := v.(*AttachPropertyValueRequest); i { case 0: return &v.state case 1: @@ -1184,7 +1177,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PatientPropertyMatcher); i { + switch v := v.(*AttachPropertyValueResponse); i { case 0: return &v.state case 1: @@ -1196,7 +1189,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAttachedPropertyValuesRequest); i { + switch v := v.(*TaskPropertyMatcher); i { case 0: return &v.state case 1: @@ -1208,7 +1201,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAttachedPropertyValuesResponse); i { + switch v := v.(*PatientPropertyMatcher); i { case 0: return &v.state case 1: @@ -1220,7 +1213,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttachPropertyValueRequest_MultiSelectValue); i { + switch v := v.(*GetAttachedPropertyValuesRequest); i { case 0: return &v.state case 1: @@ -1232,7 +1225,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAttachedPropertyValuesResponse_Value); i { + switch v := v.(*GetAttachedPropertyValuesResponse); i { case 0: return &v.state case 1: @@ -1244,7 +1237,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAttachedPropertyValuesResponse_Value_SelectValueOption); i { + switch v := v.(*AttachPropertyValueRequest_MultiSelectValue); i { case 0: return &v.state case 1: @@ -1256,7 +1249,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } file_services_property_svc_v1_property_value_svc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAttachedPropertyValuesResponse_Value_MultiSelectValue); i { + switch v := v.(*GetAttachedPropertyValuesResponse_Value); i { case 0: return &v.state case 1: @@ -1268,7 +1261,7 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { } } } - file_services_property_svc_v1_property_value_svc_proto_msgTypes[0].OneofWrappers = []interface{}{ + file_services_property_svc_v1_property_value_svc_proto_msgTypes[2].OneofWrappers = []interface{}{ (*AttachPropertyValueRequest_TextValue)(nil), (*AttachPropertyValueRequest_NumberValue)(nil), (*AttachPropertyValueRequest_BoolValue)(nil), @@ -1277,21 +1270,21 @@ func file_services_property_svc_v1_property_value_svc_proto_init() { (*AttachPropertyValueRequest_SelectValue)(nil), (*AttachPropertyValueRequest_MultiSelectValue_)(nil), } - file_services_property_svc_v1_property_value_svc_proto_msgTypes[1].OneofWrappers = []interface{}{} - file_services_property_svc_v1_property_value_svc_proto_msgTypes[2].OneofWrappers = []interface{}{} file_services_property_svc_v1_property_value_svc_proto_msgTypes[3].OneofWrappers = []interface{}{} - file_services_property_svc_v1_property_value_svc_proto_msgTypes[4].OneofWrappers = []interface{}{ + file_services_property_svc_v1_property_value_svc_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_services_property_svc_v1_property_value_svc_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_services_property_svc_v1_property_value_svc_proto_msgTypes[6].OneofWrappers = []interface{}{ (*GetAttachedPropertyValuesRequest_TaskMatcher)(nil), (*GetAttachedPropertyValuesRequest_PatientMatcher)(nil), } - file_services_property_svc_v1_property_value_svc_proto_msgTypes[7].OneofWrappers = []interface{}{ + file_services_property_svc_v1_property_value_svc_proto_msgTypes[9].OneofWrappers = []interface{}{ (*GetAttachedPropertyValuesResponse_Value_TextValue)(nil), (*GetAttachedPropertyValuesResponse_Value_NumberValue)(nil), (*GetAttachedPropertyValuesResponse_Value_BoolValue)(nil), (*GetAttachedPropertyValuesResponse_Value_DateValue)(nil), (*GetAttachedPropertyValuesResponse_Value_DateTimeValue)(nil), (*GetAttachedPropertyValuesResponse_Value_SelectValue)(nil), - (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue_)(nil), + (*GetAttachedPropertyValuesResponse_Value_MultiSelectValue)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/gen/ts/libs/common/v1/conflict_pb.d.ts b/gen/ts/libs/common/v1/conflict_pb.d.ts index 1c556cdd3..baad5aecb 100644 --- a/gen/ts/libs/common/v1/conflict_pb.d.ts +++ b/gen/ts/libs/common/v1/conflict_pb.d.ts @@ -51,3 +51,23 @@ export namespace AttributeConflict { } } +export class AnyArray extends jspb.Message { + getElementsList(): Array; + setElementsList(value: Array): AnyArray; + clearElementsList(): AnyArray; + addElements(value?: google_protobuf_any_pb.Any, index?: number): google_protobuf_any_pb.Any; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): AnyArray.AsObject; + static toObject(includeInstance: boolean, msg: AnyArray): AnyArray.AsObject; + static serializeBinaryToWriter(message: AnyArray, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): AnyArray; + static deserializeBinaryFromReader(message: AnyArray, reader: jspb.BinaryReader): AnyArray; +} + +export namespace AnyArray { + export type AsObject = { + elementsList: Array, + } +} + diff --git a/gen/ts/libs/common/v1/conflict_pb.js b/gen/ts/libs/common/v1/conflict_pb.js index be9273bb5..495b0911e 100644 --- a/gen/ts/libs/common/v1/conflict_pb.js +++ b/gen/ts/libs/common/v1/conflict_pb.js @@ -23,6 +23,7 @@ var global = var google_protobuf_any_pb = require('google-protobuf/google/protobuf/any_pb.js'); goog.object.extend(proto, google_protobuf_any_pb); +goog.exportSymbol('proto.libs.common.v1.AnyArray', null, global); goog.exportSymbol('proto.libs.common.v1.AttributeConflict', null, global); goog.exportSymbol('proto.libs.common.v1.Conflict', null, global); /** @@ -67,6 +68,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.libs.common.v1.AttributeConflict.displayName = 'proto.libs.common.v1.AttributeConflict'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.libs.common.v1.AnyArray = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.libs.common.v1.AnyArray.repeatedFields_, null); +}; +goog.inherits(proto.libs.common.v1.AnyArray, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.libs.common.v1.AnyArray.displayName = 'proto.libs.common.v1.AnyArray'; +} @@ -433,4 +455,164 @@ proto.libs.common.v1.AttributeConflict.prototype.hasWant = function() { }; + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.libs.common.v1.AnyArray.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.libs.common.v1.AnyArray.prototype.toObject = function(opt_includeInstance) { + return proto.libs.common.v1.AnyArray.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.libs.common.v1.AnyArray} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.libs.common.v1.AnyArray.toObject = function(includeInstance, msg) { + var f, obj = { + elementsList: jspb.Message.toObjectList(msg.getElementsList(), + google_protobuf_any_pb.Any.toObject, includeInstance) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.libs.common.v1.AnyArray} + */ +proto.libs.common.v1.AnyArray.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.libs.common.v1.AnyArray; + return proto.libs.common.v1.AnyArray.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.libs.common.v1.AnyArray} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.libs.common.v1.AnyArray} + */ +proto.libs.common.v1.AnyArray.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new google_protobuf_any_pb.Any; + reader.readMessage(value,google_protobuf_any_pb.Any.deserializeBinaryFromReader); + msg.addElements(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.libs.common.v1.AnyArray.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.libs.common.v1.AnyArray.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.libs.common.v1.AnyArray} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.libs.common.v1.AnyArray.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getElementsList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 1, + f, + google_protobuf_any_pb.Any.serializeBinaryToWriter + ); + } +}; + + +/** + * repeated google.protobuf.Any elements = 1; + * @return {!Array} + */ +proto.libs.common.v1.AnyArray.prototype.getElementsList = function() { + return /** @type{!Array} */ ( + jspb.Message.getRepeatedWrapperField(this, google_protobuf_any_pb.Any, 1)); +}; + + +/** + * @param {!Array} value + * @return {!proto.libs.common.v1.AnyArray} returns this +*/ +proto.libs.common.v1.AnyArray.prototype.setElementsList = function(value) { + return jspb.Message.setRepeatedWrapperField(this, 1, value); +}; + + +/** + * @param {!proto.google.protobuf.Any=} opt_value + * @param {number=} opt_index + * @return {!proto.google.protobuf.Any} + */ +proto.libs.common.v1.AnyArray.prototype.addElements = function(opt_value, opt_index) { + return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.google.protobuf.Any, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.libs.common.v1.AnyArray} returns this + */ +proto.libs.common.v1.AnyArray.prototype.clearElementsList = function() { + return this.setElementsList([]); +}; + + goog.object.extend(exports, proto.libs.common.v1); diff --git a/gen/ts/services/property_svc/v1/property_value_svc_pb.d.ts b/gen/ts/services/property_svc/v1/property_value_svc_pb.d.ts index 373589c48..6b9ae98ae 100644 --- a/gen/ts/services/property_svc/v1/property_value_svc_pb.d.ts +++ b/gen/ts/services/property_svc/v1/property_value_svc_pb.d.ts @@ -5,6 +5,52 @@ import * as google_protobuf_timestamp_pb from 'google-protobuf/google/protobuf/t import * as libs_common_v1_conflict_pb from '../../../libs/common/v1/conflict_pb'; // proto import: "libs/common/v1/conflict.proto" +export class SelectValueOption extends jspb.Message { + getId(): string; + setId(value: string): SelectValueOption; + + getName(): string; + setName(value: string): SelectValueOption; + + getDescription(): string; + setDescription(value: string): SelectValueOption; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SelectValueOption.AsObject; + static toObject(includeInstance: boolean, msg: SelectValueOption): SelectValueOption.AsObject; + static serializeBinaryToWriter(message: SelectValueOption, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SelectValueOption; + static deserializeBinaryFromReader(message: SelectValueOption, reader: jspb.BinaryReader): SelectValueOption; +} + +export namespace SelectValueOption { + export type AsObject = { + id: string, + name: string, + description: string, + } +} + +export class MultiSelectValue extends jspb.Message { + getSelectValuesList(): Array; + setSelectValuesList(value: Array): MultiSelectValue; + clearSelectValuesList(): MultiSelectValue; + addSelectValues(value?: SelectValueOption, index?: number): SelectValueOption; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): MultiSelectValue.AsObject; + static toObject(includeInstance: boolean, msg: MultiSelectValue): MultiSelectValue.AsObject; + static serializeBinaryToWriter(message: MultiSelectValue, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): MultiSelectValue; + static deserializeBinaryFromReader(message: MultiSelectValue, reader: jspb.BinaryReader): MultiSelectValue; +} + +export namespace MultiSelectValue { + export type AsObject = { + selectValuesList: Array, + } +} + export class AttachPropertyValueRequest extends jspb.Message { getSubjectId(): string; setSubjectId(value: string): AttachPropertyValueRequest; @@ -307,13 +353,13 @@ export namespace GetAttachedPropertyValuesResponse { hasDateTimeValue(): boolean; clearDateTimeValue(): Value; - getSelectValue(): GetAttachedPropertyValuesResponse.Value.SelectValueOption | undefined; - setSelectValue(value?: GetAttachedPropertyValuesResponse.Value.SelectValueOption): Value; + getSelectValue(): SelectValueOption | undefined; + setSelectValue(value?: SelectValueOption): Value; hasSelectValue(): boolean; clearSelectValue(): Value; - getMultiSelectValue(): GetAttachedPropertyValuesResponse.Value.MultiSelectValue | undefined; - setMultiSelectValue(value?: GetAttachedPropertyValuesResponse.Value.MultiSelectValue): Value; + getMultiSelectValue(): MultiSelectValue | undefined; + setMultiSelectValue(value?: MultiSelectValue): Value; hasMultiSelectValue(): boolean; clearMultiSelectValue(): Value; @@ -347,60 +393,12 @@ export namespace GetAttachedPropertyValuesResponse { boolValue: boolean, dateValue?: services_property_svc_v1_types_pb.Date.AsObject, dateTimeValue?: google_protobuf_timestamp_pb.Timestamp.AsObject, - selectValue?: GetAttachedPropertyValuesResponse.Value.SelectValueOption.AsObject, - multiSelectValue?: GetAttachedPropertyValuesResponse.Value.MultiSelectValue.AsObject, + selectValue?: SelectValueOption.AsObject, + multiSelectValue?: MultiSelectValue.AsObject, propertyConsistency: string, valueConsistency?: string, } - export class SelectValueOption extends jspb.Message { - getId(): string; - setId(value: string): SelectValueOption; - - getName(): string; - setName(value: string): SelectValueOption; - - getDescription(): string; - setDescription(value: string): SelectValueOption; - - serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): SelectValueOption.AsObject; - static toObject(includeInstance: boolean, msg: SelectValueOption): SelectValueOption.AsObject; - static serializeBinaryToWriter(message: SelectValueOption, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): SelectValueOption; - static deserializeBinaryFromReader(message: SelectValueOption, reader: jspb.BinaryReader): SelectValueOption; - } - - export namespace SelectValueOption { - export type AsObject = { - id: string, - name: string, - description: string, - } - } - - - export class MultiSelectValue extends jspb.Message { - getSelectValuesList(): Array; - setSelectValuesList(value: Array): MultiSelectValue; - clearSelectValuesList(): MultiSelectValue; - addSelectValues(value?: GetAttachedPropertyValuesResponse.Value.SelectValueOption, index?: number): GetAttachedPropertyValuesResponse.Value.SelectValueOption; - - serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): MultiSelectValue.AsObject; - static toObject(includeInstance: boolean, msg: MultiSelectValue): MultiSelectValue.AsObject; - static serializeBinaryToWriter(message: MultiSelectValue, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): MultiSelectValue; - static deserializeBinaryFromReader(message: MultiSelectValue, reader: jspb.BinaryReader): MultiSelectValue; - } - - export namespace MultiSelectValue { - export type AsObject = { - selectValuesList: Array, - } - } - - export enum ValueCase { VALUE_NOT_SET = 0, TEXT_VALUE = 6, diff --git a/gen/ts/services/property_svc/v1/property_value_svc_pb.js b/gen/ts/services/property_svc/v1/property_value_svc_pb.js index 469399ba5..21e796b49 100644 --- a/gen/ts/services/property_svc/v1/property_value_svc_pb.js +++ b/gen/ts/services/property_svc/v1/property_value_svc_pb.js @@ -35,10 +35,10 @@ goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesReque goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase', null, global); goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesResponse', null, global); goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value', null, global); -goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue', null, global); -goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption', null, global); goog.exportSymbol('proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase', null, global); +goog.exportSymbol('proto.services.property_svc.v1.MultiSelectValue', null, global); goog.exportSymbol('proto.services.property_svc.v1.PatientPropertyMatcher', null, global); +goog.exportSymbol('proto.services.property_svc.v1.SelectValueOption', null, global); goog.exportSymbol('proto.services.property_svc.v1.TaskPropertyMatcher', null, global); /** * Generated by JsPbCodeGenerator. @@ -50,16 +50,16 @@ goog.exportSymbol('proto.services.property_svc.v1.TaskPropertyMatcher', null, gl * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.AttachPropertyValueRequest = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_); +proto.services.property_svc.v1.SelectValueOption = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.services.property_svc.v1.AttachPropertyValueRequest, jspb.Message); +goog.inherits(proto.services.property_svc.v1.SelectValueOption, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.AttachPropertyValueRequest.displayName = 'proto.services.property_svc.v1.AttachPropertyValueRequest'; + proto.services.property_svc.v1.SelectValueOption.displayName = 'proto.services.property_svc.v1.SelectValueOption'; } /** * Generated by JsPbCodeGenerator. @@ -71,16 +71,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.repeatedFields_, null); +proto.services.property_svc.v1.MultiSelectValue = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.services.property_svc.v1.MultiSelectValue.repeatedFields_, null); }; -goog.inherits(proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue, jspb.Message); +goog.inherits(proto.services.property_svc.v1.MultiSelectValue, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.displayName = 'proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue'; + proto.services.property_svc.v1.MultiSelectValue.displayName = 'proto.services.property_svc.v1.MultiSelectValue'; } /** * Generated by JsPbCodeGenerator. @@ -92,16 +92,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.AttachPropertyValueResponse = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); +proto.services.property_svc.v1.AttachPropertyValueRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_); }; -goog.inherits(proto.services.property_svc.v1.AttachPropertyValueResponse, jspb.Message); +goog.inherits(proto.services.property_svc.v1.AttachPropertyValueRequest, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.AttachPropertyValueResponse.displayName = 'proto.services.property_svc.v1.AttachPropertyValueResponse'; + proto.services.property_svc.v1.AttachPropertyValueRequest.displayName = 'proto.services.property_svc.v1.AttachPropertyValueRequest'; } /** * Generated by JsPbCodeGenerator. @@ -113,16 +113,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.TaskPropertyMatcher = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.repeatedFields_, null); }; -goog.inherits(proto.services.property_svc.v1.TaskPropertyMatcher, jspb.Message); +goog.inherits(proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.TaskPropertyMatcher.displayName = 'proto.services.property_svc.v1.TaskPropertyMatcher'; + proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.displayName = 'proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue'; } /** * Generated by JsPbCodeGenerator. @@ -134,16 +134,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.PatientPropertyMatcher = function(opt_data) { +proto.services.property_svc.v1.AttachPropertyValueResponse = function(opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.services.property_svc.v1.PatientPropertyMatcher, jspb.Message); +goog.inherits(proto.services.property_svc.v1.AttachPropertyValueResponse, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.PatientPropertyMatcher.displayName = 'proto.services.property_svc.v1.PatientPropertyMatcher'; + proto.services.property_svc.v1.AttachPropertyValueResponse.displayName = 'proto.services.property_svc.v1.AttachPropertyValueResponse'; } /** * Generated by JsPbCodeGenerator. @@ -155,16 +155,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_); +proto.services.property_svc.v1.TaskPropertyMatcher = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesRequest, jspb.Message); +goog.inherits(proto.services.property_svc.v1.TaskPropertyMatcher, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesRequest'; + proto.services.property_svc.v1.TaskPropertyMatcher.displayName = 'proto.services.property_svc.v1.TaskPropertyMatcher'; } /** * Generated by JsPbCodeGenerator. @@ -176,16 +176,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.repeatedFields_, null); +proto.services.property_svc.v1.PatientPropertyMatcher = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesResponse, jspb.Message); +goog.inherits(proto.services.property_svc.v1.PatientPropertyMatcher, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesResponse'; + proto.services.property_svc.v1.PatientPropertyMatcher.displayName = 'proto.services.property_svc.v1.PatientPropertyMatcher'; } /** * Generated by JsPbCodeGenerator. @@ -197,16 +197,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.oneofGroups_); +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_); }; -goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value, jspb.Message); +goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesRequest, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value'; + proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesRequest'; } /** * Generated by JsPbCodeGenerator. @@ -218,16 +218,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.repeatedFields_, null); }; -goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption, jspb.Message); +goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesResponse, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption'; + proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesResponse'; } /** * Generated by JsPbCodeGenerator. @@ -239,49 +239,18 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.repeatedFields_, null); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.oneofGroups_); }; -goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue, jspb.Message); +goog.inherits(proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue'; + proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.displayName = 'proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value'; } -/** - * Oneof group definitions for this message. Each group defines the field - * numbers belonging to that group. When of these fields' value is set, all - * other fields in the group are cleared. During deserialization, if multiple - * fields are encountered for a group, only the last value seen will be kept. - * @private {!Array>} - * @const - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_ = [[3,4,5,6,7,8,9]]; - -/** - * @enum {number} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.ValueCase = { - VALUE_NOT_SET: 0, - TEXT_VALUE: 3, - NUMBER_VALUE: 4, - BOOL_VALUE: 5, - DATE_VALUE: 6, - DATE_TIME_VALUE: 7, - SELECT_VALUE: 8, - MULTI_SELECT_VALUE: 9 -}; - -/** - * @return {proto.services.property_svc.v1.AttachPropertyValueRequest.ValueCase} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getValueCase = function() { - return /** @type {proto.services.property_svc.v1.AttachPropertyValueRequest.ValueCase} */(jspb.Message.computeOneofCase(this, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0])); -}; - if (jspb.Message.GENERATE_TO_OBJECT) { @@ -297,8 +266,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.AttachPropertyValueRequest.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.SelectValueOption.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.SelectValueOption.toObject(opt_includeInstance, this); }; @@ -307,22 +276,15 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.toObject = f * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.SelectValueOption} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.AttachPropertyValueRequest.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.SelectValueOption.toObject = function(includeInstance, msg) { var f, obj = { - subjectId: jspb.Message.getFieldWithDefault(msg, 1, ""), - propertyId: jspb.Message.getFieldWithDefault(msg, 2, ""), - textValue: jspb.Message.getFieldWithDefault(msg, 3, ""), - numberValue: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0), - boolValue: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), - dateValue: (f = msg.getDateValue()) && services_property_svc_v1_types_pb.Date.toObject(includeInstance, f), - dateTimeValue: (f = msg.getDateTimeValue()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f), - selectValue: jspb.Message.getFieldWithDefault(msg, 8, ""), - multiSelectValue: (f = msg.getMultiSelectValue()) && proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObject(includeInstance, f), - consistency: jspb.Message.getFieldWithDefault(msg, 10, "") + id: jspb.Message.getFieldWithDefault(msg, 1, ""), + name: jspb.Message.getFieldWithDefault(msg, 2, ""), + description: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -336,23 +298,23 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.toObject = function(in /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} + * @return {!proto.services.property_svc.v1.SelectValueOption} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.SelectValueOption.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.AttachPropertyValueRequest; - return proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.SelectValueOption; + return proto.services.property_svc.v1.SelectValueOption.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.SelectValueOption} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} + * @return {!proto.services.property_svc.v1.SelectValueOption} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.SelectValueOption.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -361,46 +323,15 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinaryFromR switch (field) { case 1: var value = /** @type {string} */ (reader.readString()); - msg.setSubjectId(value); + msg.setId(value); break; case 2: var value = /** @type {string} */ (reader.readString()); - msg.setPropertyId(value); + msg.setName(value); break; case 3: var value = /** @type {string} */ (reader.readString()); - msg.setTextValue(value); - break; - case 4: - var value = /** @type {number} */ (reader.readDouble()); - msg.setNumberValue(value); - break; - case 5: - var value = /** @type {boolean} */ (reader.readBool()); - msg.setBoolValue(value); - break; - case 6: - var value = new services_property_svc_v1_types_pb.Date; - reader.readMessage(value,services_property_svc_v1_types_pb.Date.deserializeBinaryFromReader); - msg.setDateValue(value); - break; - case 7: - var value = new google_protobuf_timestamp_pb.Timestamp; - reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader); - msg.setDateTimeValue(value); - break; - case 8: - var value = /** @type {string} */ (reader.readString()); - msg.setSelectValue(value); - break; - case 9: - var value = new proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue; - reader.readMessage(value,proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinaryFromReader); - msg.setMultiSelectValue(value); - break; - case 10: - var value = /** @type {string} */ (reader.readString()); - msg.setConsistency(value); + msg.setDescription(value); break; default: reader.skipField(); @@ -415,9 +346,9 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinaryFromR * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.serializeBinary = function() { +proto.services.property_svc.v1.SelectValueOption.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.AttachPropertyValueRequest.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.SelectValueOption.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -425,113 +356,115 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.serializeBin /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest} message + * @param {!proto.services.property_svc.v1.SelectValueOption} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.AttachPropertyValueRequest.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.SelectValueOption.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getSubjectId(); + f = message.getId(); if (f.length > 0) { writer.writeString( 1, f ); } - f = message.getPropertyId(); + f = message.getName(); if (f.length > 0) { writer.writeString( 2, f ); } - f = /** @type {string} */ (jspb.Message.getField(message, 3)); - if (f != null) { + f = message.getDescription(); + if (f.length > 0) { writer.writeString( 3, f ); } - f = /** @type {number} */ (jspb.Message.getField(message, 4)); - if (f != null) { - writer.writeDouble( - 4, - f - ); - } - f = /** @type {boolean} */ (jspb.Message.getField(message, 5)); - if (f != null) { - writer.writeBool( - 5, - f - ); - } - f = message.getDateValue(); - if (f != null) { - writer.writeMessage( - 6, - f, - services_property_svc_v1_types_pb.Date.serializeBinaryToWriter - ); - } - f = message.getDateTimeValue(); - if (f != null) { - writer.writeMessage( - 7, - f, - google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter - ); - } - f = /** @type {string} */ (jspb.Message.getField(message, 8)); - if (f != null) { - writer.writeString( - 8, - f - ); - } - f = message.getMultiSelectValue(); - if (f != null) { - writer.writeMessage( - 9, - f, - proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.serializeBinaryToWriter - ); - } - f = /** @type {string} */ (jspb.Message.getField(message, 10)); - if (f != null) { - writer.writeString( - 10, - f - ); - } }; +/** + * optional string id = 1; + * @return {string} + */ +proto.services.property_svc.v1.SelectValueOption.prototype.getId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + /** - * List of repeated fields within this message type. - * @private {!Array} - * @const + * @param {string} value + * @return {!proto.services.property_svc.v1.SelectValueOption} returns this */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.repeatedFields_ = [1,2]; +proto.services.property_svc.v1.SelectValueOption.prototype.setId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; +/** + * optional string name = 2; + * @return {string} + */ +proto.services.property_svc.v1.SelectValueOption.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + -if (jspb.Message.GENERATE_TO_OBJECT) { /** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} + * @param {string} value + * @return {!proto.services.property_svc.v1.SelectValueOption} returns this */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.SelectValueOption.prototype.setName = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string description = 3; + * @return {string} + */ +proto.services.property_svc.v1.SelectValueOption.prototype.getDescription = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.property_svc.v1.SelectValueOption} returns this + */ +proto.services.property_svc.v1.SelectValueOption.prototype.setDescription = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.services.property_svc.v1.MultiSelectValue.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.property_svc.v1.MultiSelectValue.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.MultiSelectValue.toObject(opt_includeInstance, this); }; @@ -540,14 +473,14 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.proto * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.MultiSelectValue} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.MultiSelectValue.toObject = function(includeInstance, msg) { var f, obj = { - selectValuesList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, - removeSelectValuesList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f + selectValuesList: jspb.Message.toObjectList(msg.getSelectValuesList(), + proto.services.property_svc.v1.SelectValueOption.toObject, includeInstance) }; if (includeInstance) { @@ -561,23 +494,23 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObj /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} + * @return {!proto.services.property_svc.v1.MultiSelectValue} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.MultiSelectValue.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue; - return proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.MultiSelectValue; + return proto.services.property_svc.v1.MultiSelectValue.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.MultiSelectValue} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} + * @return {!proto.services.property_svc.v1.MultiSelectValue} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.MultiSelectValue.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -585,13 +518,10 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deser var field = reader.getFieldNumber(); switch (field) { case 1: - var value = /** @type {string} */ (reader.readString()); + var value = new proto.services.property_svc.v1.SelectValueOption; + reader.readMessage(value,proto.services.property_svc.v1.SelectValueOption.deserializeBinaryFromReader); msg.addSelectValues(value); break; - case 2: - var value = /** @type {string} */ (reader.readString()); - msg.addRemoveSelectValues(value); - break; default: reader.skipField(); break; @@ -605,9 +535,9 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deser * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.serializeBinary = function() { +proto.services.property_svc.v1.MultiSelectValue.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.MultiSelectValue.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -615,530 +545,229 @@ proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.proto /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} message + * @param {!proto.services.property_svc.v1.MultiSelectValue} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.MultiSelectValue.serializeBinaryToWriter = function(message, writer) { var f = undefined; f = message.getSelectValuesList(); if (f.length > 0) { - writer.writeRepeatedString( + writer.writeRepeatedMessage( 1, - f - ); - } - f = message.getRemoveSelectValuesList(); - if (f.length > 0) { - writer.writeRepeatedString( - 2, - f + f, + proto.services.property_svc.v1.SelectValueOption.serializeBinaryToWriter ); } }; /** - * repeated string select_values = 1; - * @return {!Array} + * repeated SelectValueOption select_values = 1; + * @return {!Array} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.getSelectValuesList = function() { - return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +proto.services.property_svc.v1.MultiSelectValue.prototype.getSelectValuesList = function() { + return /** @type{!Array} */ ( + jspb.Message.getRepeatedWrapperField(this, proto.services.property_svc.v1.SelectValueOption, 1)); }; /** - * @param {!Array} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.setSelectValuesList = function(value) { - return jspb.Message.setField(this, 1, value || []); + * @param {!Array} value + * @return {!proto.services.property_svc.v1.MultiSelectValue} returns this +*/ +proto.services.property_svc.v1.MultiSelectValue.prototype.setSelectValuesList = function(value) { + return jspb.Message.setRepeatedWrapperField(this, 1, value); }; /** - * @param {string} value + * @param {!proto.services.property_svc.v1.SelectValueOption=} opt_value * @param {number=} opt_index - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + * @return {!proto.services.property_svc.v1.SelectValueOption} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.addSelectValues = function(value, opt_index) { - return jspb.Message.addToRepeatedField(this, 1, value, opt_index); +proto.services.property_svc.v1.MultiSelectValue.prototype.addSelectValues = function(opt_value, opt_index) { + return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.services.property_svc.v1.SelectValueOption, opt_index); }; /** * Clears the list making it empty but non-null. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + * @return {!proto.services.property_svc.v1.MultiSelectValue} returns this */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.clearSelectValuesList = function() { +proto.services.property_svc.v1.MultiSelectValue.prototype.clearSelectValuesList = function() { return this.setSelectValuesList([]); }; + /** - * repeated string remove_select_values = 2; - * @return {!Array} + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.getRemoveSelectValuesList = function() { - return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); -}; - +proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_ = [[3,4,5,6,7,8,9]]; /** - * @param {!Array} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + * @enum {number} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.setRemoveSelectValuesList = function(value) { - return jspb.Message.setField(this, 2, value || []); +proto.services.property_svc.v1.AttachPropertyValueRequest.ValueCase = { + VALUE_NOT_SET: 0, + TEXT_VALUE: 3, + NUMBER_VALUE: 4, + BOOL_VALUE: 5, + DATE_VALUE: 6, + DATE_TIME_VALUE: 7, + SELECT_VALUE: 8, + MULTI_SELECT_VALUE: 9 }; - /** - * @param {string} value - * @param {number=} opt_index - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + * @return {proto.services.property_svc.v1.AttachPropertyValueRequest.ValueCase} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.addRemoveSelectValues = function(value, opt_index) { - return jspb.Message.addToRepeatedField(this, 2, value, opt_index); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getValueCase = function() { + return /** @type {proto.services.property_svc.v1.AttachPropertyValueRequest.ValueCase} */(jspb.Message.computeOneofCase(this, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0])); }; + +if (jspb.Message.GENERATE_TO_OBJECT) { /** - * Clears the list making it empty but non-null. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.clearRemoveSelectValuesList = function() { - return this.setRemoveSelectValuesList([]); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.AttachPropertyValueRequest.toObject(opt_includeInstance, this); }; /** - * optional string subject_id = 1; - * @return {string} + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getSubjectId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +proto.services.property_svc.v1.AttachPropertyValueRequest.toObject = function(includeInstance, msg) { + var f, obj = { + subjectId: jspb.Message.getFieldWithDefault(msg, 1, ""), + propertyId: jspb.Message.getFieldWithDefault(msg, 2, ""), + textValue: jspb.Message.getFieldWithDefault(msg, 3, ""), + numberValue: jspb.Message.getFloatingPointFieldWithDefault(msg, 4, 0.0), + boolValue: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), + dateValue: (f = msg.getDateValue()) && services_property_svc_v1_types_pb.Date.toObject(includeInstance, f), + dateTimeValue: (f = msg.getDateTimeValue()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f), + selectValue: jspb.Message.getFieldWithDefault(msg, 8, ""), + multiSelectValue: (f = msg.getMultiSelectValue()) && proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObject(includeInstance, f), + consistency: jspb.Message.getFieldWithDefault(msg, 10, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; }; +} /** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setSubjectId = function(value) { - return jspb.Message.setProto3StringField(this, 1, value); +proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.property_svc.v1.AttachPropertyValueRequest; + return proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinaryFromReader(msg, reader); }; /** - * optional string property_id = 2; - * @return {string} + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getPropertyId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +proto.services.property_svc.v1.AttachPropertyValueRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setSubjectId(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setPropertyId(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setTextValue(value); + break; + case 4: + var value = /** @type {number} */ (reader.readDouble()); + msg.setNumberValue(value); + break; + case 5: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setBoolValue(value); + break; + case 6: + var value = new services_property_svc_v1_types_pb.Date; + reader.readMessage(value,services_property_svc_v1_types_pb.Date.deserializeBinaryFromReader); + msg.setDateValue(value); + break; + case 7: + var value = new google_protobuf_timestamp_pb.Timestamp; + reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader); + msg.setDateTimeValue(value); + break; + case 8: + var value = /** @type {string} */ (reader.readString()); + msg.setSelectValue(value); + break; + case 9: + var value = new proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue; + reader.readMessage(value,proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinaryFromReader); + msg.setMultiSelectValue(value); + break; + case 10: + var value = /** @type {string} */ (reader.readString()); + msg.setConsistency(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; }; /** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setPropertyId = function(value) { - return jspb.Message.setProto3StringField(this, 2, value); -}; - - -/** - * optional string text_value = 3; - * @return {string} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getTextValue = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - - -/** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setTextValue = function(value) { - return jspb.Message.setOneofField(this, 3, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearTextValue = function() { - return jspb.Message.setOneofField(this, 3, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasTextValue = function() { - return jspb.Message.getField(this, 3) != null; -}; - - -/** - * optional double number_value = 4; - * @return {number} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getNumberValue = function() { - return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 4, 0.0)); -}; - - -/** - * @param {number} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setNumberValue = function(value) { - return jspb.Message.setOneofField(this, 4, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearNumberValue = function() { - return jspb.Message.setOneofField(this, 4, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasNumberValue = function() { - return jspb.Message.getField(this, 4) != null; -}; - - -/** - * optional bool bool_value = 5; - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getBoolValue = function() { - return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false)); -}; - - -/** - * @param {boolean} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setBoolValue = function(value) { - return jspb.Message.setOneofField(this, 5, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearBoolValue = function() { - return jspb.Message.setOneofField(this, 5, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasBoolValue = function() { - return jspb.Message.getField(this, 5) != null; -}; - - -/** - * optional Date date_value = 6; - * @return {?proto.services.property_svc.v1.Date} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getDateValue = function() { - return /** @type{?proto.services.property_svc.v1.Date} */ ( - jspb.Message.getWrapperField(this, services_property_svc_v1_types_pb.Date, 6)); -}; - - -/** - * @param {?proto.services.property_svc.v1.Date|undefined} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this -*/ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setDateValue = function(value) { - return jspb.Message.setOneofWrapperField(this, 6, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearDateValue = function() { - return this.setDateValue(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasDateValue = function() { - return jspb.Message.getField(this, 6) != null; -}; - - -/** - * optional google.protobuf.Timestamp date_time_value = 7; - * @return {?proto.google.protobuf.Timestamp} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getDateTimeValue = function() { - return /** @type{?proto.google.protobuf.Timestamp} */ ( - jspb.Message.getWrapperField(this, google_protobuf_timestamp_pb.Timestamp, 7)); -}; - - -/** - * @param {?proto.google.protobuf.Timestamp|undefined} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this -*/ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setDateTimeValue = function(value) { - return jspb.Message.setOneofWrapperField(this, 7, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearDateTimeValue = function() { - return this.setDateTimeValue(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasDateTimeValue = function() { - return jspb.Message.getField(this, 7) != null; -}; - - -/** - * optional string select_value = 8; - * @return {string} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getSelectValue = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "")); -}; - - -/** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setSelectValue = function(value) { - return jspb.Message.setOneofField(this, 8, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearSelectValue = function() { - return jspb.Message.setOneofField(this, 8, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasSelectValue = function() { - return jspb.Message.getField(this, 8) != null; -}; - - -/** - * optional MultiSelectValue multi_select_value = 9; - * @return {?proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getMultiSelectValue = function() { - return /** @type{?proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} */ ( - jspb.Message.getWrapperField(this, proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue, 9)); -}; - - -/** - * @param {?proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue|undefined} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this -*/ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setMultiSelectValue = function(value) { - return jspb.Message.setOneofWrapperField(this, 9, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearMultiSelectValue = function() { - return this.setMultiSelectValue(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasMultiSelectValue = function() { - return jspb.Message.getField(this, 9) != null; -}; - - -/** - * optional string consistency = 10; - * @return {string} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getConsistency = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 10, "")); -}; - - -/** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setConsistency = function(value) { - return jspb.Message.setField(this, 10, value); -}; - - -/** - * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearConsistency = function() { - return jspb.Message.setField(this, 10, undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasConsistency = function() { - return jspb.Message.getField(this, 10) != null; -}; - - - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.AttachPropertyValueResponse.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.AttachPropertyValueResponse} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.toObject = function(includeInstance, msg) { - var f, obj = { - propertyValueId: jspb.Message.getFieldWithDefault(msg, 1, ""), - conflict: (f = msg.getConflict()) && libs_common_v1_conflict_pb.Conflict.toObject(includeInstance, f), - consistency: jspb.Message.getFieldWithDefault(msg, 3, "") - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.AttachPropertyValueResponse; - return proto.services.property_svc.v1.AttachPropertyValueResponse.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.services.property_svc.v1.AttachPropertyValueResponse} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {string} */ (reader.readString()); - msg.setPropertyValueId(value); - break; - case 2: - var value = new libs_common_v1_conflict_pb.Conflict; - reader.readMessage(value,libs_common_v1_conflict_pb.Conflict.deserializeBinaryFromReader); - msg.setConflict(value); - break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setConsistency(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.serializeBinary = function() { +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.AttachPropertyValueResponse.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.AttachPropertyValueRequest.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1146,110 +775,95 @@ proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.serializeBi /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.AttachPropertyValueResponse} message + * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.AttachPropertyValueResponse.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.AttachPropertyValueRequest.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getPropertyValueId(); + f = message.getSubjectId(); if (f.length > 0) { writer.writeString( 1, f ); } - f = message.getConflict(); + f = message.getPropertyId(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 3)); + if (f != null) { + writer.writeString( + 3, + f + ); + } + f = /** @type {number} */ (jspb.Message.getField(message, 4)); + if (f != null) { + writer.writeDouble( + 4, + f + ); + } + f = /** @type {boolean} */ (jspb.Message.getField(message, 5)); + if (f != null) { + writer.writeBool( + 5, + f + ); + } + f = message.getDateValue(); if (f != null) { writer.writeMessage( - 2, + 6, f, - libs_common_v1_conflict_pb.Conflict.serializeBinaryToWriter + services_property_svc_v1_types_pb.Date.serializeBinaryToWriter ); } - f = message.getConsistency(); - if (f.length > 0) { + f = message.getDateTimeValue(); + if (f != null) { + writer.writeMessage( + 7, + f, + google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 8)); + if (f != null) { writer.writeString( - 3, + 8, + f + ); + } + f = message.getMultiSelectValue(); + if (f != null) { + writer.writeMessage( + 9, + f, + proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.serializeBinaryToWriter + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 10)); + if (f != null) { + writer.writeString( + 10, f ); } }; -/** - * optional string property_value_id = 1; - * @return {string} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.getPropertyValueId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); -}; - - -/** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.setPropertyValueId = function(value) { - return jspb.Message.setProto3StringField(this, 1, value); -}; - - -/** - * optional libs.common.v1.Conflict conflict = 2; - * @return {?proto.libs.common.v1.Conflict} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.getConflict = function() { - return /** @type{?proto.libs.common.v1.Conflict} */ ( - jspb.Message.getWrapperField(this, libs_common_v1_conflict_pb.Conflict, 2)); -}; - - -/** - * @param {?proto.libs.common.v1.Conflict|undefined} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this -*/ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.setConflict = function(value) { - return jspb.Message.setWrapperField(this, 2, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.clearConflict = function() { - return this.setConflict(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.hasConflict = function() { - return jspb.Message.getField(this, 2) != null; -}; - - -/** - * optional string consistency = 3; - * @return {string} - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.getConsistency = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - /** - * @param {string} value - * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this - */ -proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.setConsistency = function(value) { - return jspb.Message.setProto3StringField(this, 3, value); -}; - - + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.repeatedFields_ = [1,2]; @@ -1266,8 +880,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.TaskPropertyMatcher.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObject(opt_includeInstance, this); }; @@ -1276,14 +890,14 @@ proto.services.property_svc.v1.TaskPropertyMatcher.prototype.toObject = function * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.TaskPropertyMatcher} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.TaskPropertyMatcher.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.toObject = function(includeInstance, msg) { var f, obj = { - wardId: jspb.Message.getFieldWithDefault(msg, 1, ""), - taskId: jspb.Message.getFieldWithDefault(msg, 2, "") + selectValuesList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, + removeSelectValuesList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f }; if (includeInstance) { @@ -1297,23 +911,23 @@ proto.services.property_svc.v1.TaskPropertyMatcher.toObject = function(includeIn /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} */ -proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.TaskPropertyMatcher; - return proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue; + return proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.TaskPropertyMatcher} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} */ -proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -1322,11 +936,11 @@ proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader = switch (field) { case 1: var value = /** @type {string} */ (reader.readString()); - msg.setWardId(value); + msg.addSelectValues(value); break; case 2: var value = /** @type {string} */ (reader.readString()); - msg.setTaskId(value); + msg.addRemoveSelectValues(value); break; default: reader.skipField(); @@ -1341,9 +955,9 @@ proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader = * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.serializeBinary = function() { +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1351,22 +965,22 @@ proto.services.property_svc.v1.TaskPropertyMatcher.prototype.serializeBinary = f /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.TaskPropertyMatcher} message + * @param {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = /** @type {string} */ (jspb.Message.getField(message, 1)); - if (f != null) { - writer.writeString( + f = message.getSelectValuesList(); + if (f.length > 0) { + writer.writeRepeatedString( 1, f ); } - f = /** @type {string} */ (jspb.Message.getField(message, 2)); - if (f != null) { - writer.writeString( + f = message.getRemoveSelectValuesList(); + if (f.length > 0) { + writer.writeRepeatedString( 2, f ); @@ -1375,29 +989,139 @@ proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter = fun /** - * optional string ward_id = 1; + * repeated string select_values = 1; + * @return {!Array} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.getSelectValuesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** + * @param {!Array} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.setSelectValuesList = function(value) { + return jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.addSelectValues = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.clearSelectValuesList = function() { + return this.setSelectValuesList([]); +}; + + +/** + * repeated string remove_select_values = 2; + * @return {!Array} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.getRemoveSelectValuesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); +}; + + +/** + * @param {!Array} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.setRemoveSelectValuesList = function(value) { + return jspb.Message.setField(this, 2, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.addRemoveSelectValues = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 2, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue.prototype.clearRemoveSelectValuesList = function() { + return this.setRemoveSelectValuesList([]); +}; + + +/** + * optional string subject_id = 1; * @return {string} */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.getWardId = function() { +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getSubjectId = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** * @param {string} value - * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.setWardId = function(value) { - return jspb.Message.setField(this, 1, value); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setSubjectId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string property_id = 2; + * @return {string} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getPropertyId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setPropertyId = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string text_value = 3; + * @return {string} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getTextValue = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setTextValue = function(value) { + return jspb.Message.setOneofField(this, 3, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); }; /** * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.clearWardId = function() { - return jspb.Message.setField(this, 1, undefined); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearTextValue = function() { + return jspb.Message.setOneofField(this, 3, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); }; @@ -1405,35 +1129,35 @@ proto.services.property_svc.v1.TaskPropertyMatcher.prototype.clearWardId = funct * Returns whether this field is set. * @return {boolean} */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.hasWardId = function() { - return jspb.Message.getField(this, 1) != null; +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasTextValue = function() { + return jspb.Message.getField(this, 3) != null; }; /** - * optional string task_id = 2; - * @return {string} + * optional double number_value = 4; + * @return {number} */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.getTaskId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getNumberValue = function() { + return /** @type {number} */ (jspb.Message.getFloatingPointFieldWithDefault(this, 4, 0.0)); }; /** - * @param {string} value - * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this + * @param {number} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.setTaskId = function(value) { - return jspb.Message.setField(this, 2, value); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setNumberValue = function(value) { + return jspb.Message.setOneofField(this, 4, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); }; /** * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.clearTaskId = function() { - return jspb.Message.setField(this, 2, undefined); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearNumberValue = function() { + return jspb.Message.setOneofField(this, 4, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); }; @@ -1441,159 +1165,145 @@ proto.services.property_svc.v1.TaskPropertyMatcher.prototype.clearTaskId = funct * Returns whether this field is set. * @return {boolean} */ -proto.services.property_svc.v1.TaskPropertyMatcher.prototype.hasTaskId = function() { - return jspb.Message.getField(this, 2) != null; +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasNumberValue = function() { + return jspb.Message.getField(this, 4) != null; +}; + + +/** + * optional bool bool_value = 5; + * @return {boolean} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getBoolValue = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setBoolValue = function(value) { + return jspb.Message.setOneofField(this, 5, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearBoolValue = function() { + return jspb.Message.setOneofField(this, 5, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasBoolValue = function() { + return jspb.Message.getField(this, 5) != null; +}; + + +/** + * optional Date date_value = 6; + * @return {?proto.services.property_svc.v1.Date} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getDateValue = function() { + return /** @type{?proto.services.property_svc.v1.Date} */ ( + jspb.Message.getWrapperField(this, services_property_svc_v1_types_pb.Date, 6)); }; +/** + * @param {?proto.services.property_svc.v1.Date|undefined} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this +*/ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setDateValue = function(value) { + return jspb.Message.setOneofWrapperField(this, 6, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); +}; - -if (jspb.Message.GENERATE_TO_OBJECT) { /** - * Creates an object representation of this proto. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * Optional fields that are not set will be set to undefined. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * net/proto2/compiler/js/internal/generator.cc#kKeyword. - * @param {boolean=} opt_includeInstance Deprecated. whether to include the - * JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @return {!Object} + * Clears the message field making it undefined. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.PatientPropertyMatcher.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearDateValue = function() { + return this.setDateValue(undefined); }; /** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Deprecated. Whether to include - * the JSPB instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.PatientPropertyMatcher} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages + * Returns whether this field is set. + * @return {boolean} */ -proto.services.property_svc.v1.PatientPropertyMatcher.toObject = function(includeInstance, msg) { - var f, obj = { - wardId: jspb.Message.getFieldWithDefault(msg, 1, ""), - patientId: jspb.Message.getFieldWithDefault(msg, 2, "") - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasDateValue = function() { + return jspb.Message.getField(this, 6) != null; }; -} /** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} + * optional google.protobuf.Timestamp date_time_value = 7; + * @return {?proto.google.protobuf.Timestamp} */ -proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.PatientPropertyMatcher; - return proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinaryFromReader(msg, reader); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getDateTimeValue = function() { + return /** @type{?proto.google.protobuf.Timestamp} */ ( + jspb.Message.getWrapperField(this, google_protobuf_timestamp_pb.Timestamp, 7)); }; /** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.services.property_svc.v1.PatientPropertyMatcher} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} - */ -proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {string} */ (reader.readString()); - msg.setWardId(value); - break; - case 2: - var value = /** @type {string} */ (reader.readString()); - msg.setPatientId(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; + * @param {?proto.google.protobuf.Timestamp|undefined} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this +*/ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setDateTimeValue = function(value) { + return jspb.Message.setOneofWrapperField(this, 7, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); }; /** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} + * Clears the message field making it undefined. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.PatientPropertyMatcher.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearDateTimeValue = function() { + return this.setDateTimeValue(undefined); }; /** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.PatientPropertyMatcher} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages + * Returns whether this field is set. + * @return {boolean} */ -proto.services.property_svc.v1.PatientPropertyMatcher.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = /** @type {string} */ (jspb.Message.getField(message, 1)); - if (f != null) { - writer.writeString( - 1, - f - ); - } - f = /** @type {string} */ (jspb.Message.getField(message, 2)); - if (f != null) { - writer.writeString( - 2, - f - ); - } +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasDateTimeValue = function() { + return jspb.Message.getField(this, 7) != null; }; /** - * optional string ward_id = 1; + * optional string select_value = 8; * @return {string} */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.getWardId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getSelectValue = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "")); }; /** * @param {string} value - * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.setWardId = function(value) { - return jspb.Message.setField(this, 1, value); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setSelectValue = function(value) { + return jspb.Message.setOneofField(this, 8, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); }; /** * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.clearWardId = function() { - return jspb.Message.setField(this, 1, undefined); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearSelectValue = function() { + return jspb.Message.setOneofField(this, 8, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], undefined); }; @@ -1601,35 +1311,36 @@ proto.services.property_svc.v1.PatientPropertyMatcher.prototype.clearWardId = fu * Returns whether this field is set. * @return {boolean} */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.hasWardId = function() { - return jspb.Message.getField(this, 1) != null; +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasSelectValue = function() { + return jspb.Message.getField(this, 8) != null; }; /** - * optional string patient_id = 2; - * @return {string} + * optional MultiSelectValue multi_select_value = 9; + * @return {?proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.getPatientId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getMultiSelectValue = function() { + return /** @type{?proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue} */ ( + jspb.Message.getWrapperField(this, proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue, 9)); }; /** - * @param {string} value - * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this - */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.setPatientId = function(value) { - return jspb.Message.setField(this, 2, value); + * @param {?proto.services.property_svc.v1.AttachPropertyValueRequest.MultiSelectValue|undefined} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this +*/ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setMultiSelectValue = function(value) { + return jspb.Message.setOneofWrapperField(this, 9, proto.services.property_svc.v1.AttachPropertyValueRequest.oneofGroups_[0], value); }; /** - * Clears the field making it undefined. - * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + * Clears the message field making it undefined. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.clearPatientId = function() { - return jspb.Message.setField(this, 2, undefined); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearMultiSelectValue = function() { + return this.setMultiSelectValue(undefined); }; @@ -1637,40 +1348,50 @@ proto.services.property_svc.v1.PatientPropertyMatcher.prototype.clearPatientId = * Returns whether this field is set. * @return {boolean} */ -proto.services.property_svc.v1.PatientPropertyMatcher.prototype.hasPatientId = function() { - return jspb.Message.getField(this, 2) != null; +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasMultiSelectValue = function() { + return jspb.Message.getField(this, 9) != null; }; +/** + * optional string consistency = 10; + * @return {string} + */ +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.getConsistency = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 10, "")); +}; + /** - * Oneof group definitions for this message. Each group defines the field - * numbers belonging to that group. When of these fields' value is set, all - * other fields in the group are cleared. During deserialization, if multiple - * fields are encountered for a group, only the last value seen will be kept. - * @private {!Array>} - * @const + * @param {string} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_ = [[1,2]]; +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.setConsistency = function(value) { + return jspb.Message.setField(this, 10, value); +}; + /** - * @enum {number} + * Clears the field making it undefined. + * @return {!proto.services.property_svc.v1.AttachPropertyValueRequest} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase = { - MATCHER_NOT_SET: 0, - TASK_MATCHER: 1, - PATIENT_MATCHER: 2 +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.clearConsistency = function() { + return jspb.Message.setField(this, 10, undefined); }; + /** - * @return {proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase} + * Returns whether this field is set. + * @return {boolean} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.getMatcherCase = function() { - return /** @type {proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase} */(jspb.Message.computeOneofCase(this, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_[0])); +proto.services.property_svc.v1.AttachPropertyValueRequest.prototype.hasConsistency = function() { + return jspb.Message.getField(this, 10) != null; }; + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. @@ -1684,8 +1405,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.AttachPropertyValueResponse.toObject(opt_includeInstance, this); }; @@ -1694,14 +1415,15 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.toObje * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.AttachPropertyValueResponse} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.AttachPropertyValueResponse.toObject = function(includeInstance, msg) { var f, obj = { - taskMatcher: (f = msg.getTaskMatcher()) && proto.services.property_svc.v1.TaskPropertyMatcher.toObject(includeInstance, f), - patientMatcher: (f = msg.getPatientMatcher()) && proto.services.property_svc.v1.PatientPropertyMatcher.toObject(includeInstance, f) + propertyValueId: jspb.Message.getFieldWithDefault(msg, 1, ""), + conflict: (f = msg.getConflict()) && libs_common_v1_conflict_pb.Conflict.toObject(includeInstance, f), + consistency: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -1715,23 +1437,23 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.toObject = funct /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} + * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.AttachPropertyValueResponse.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesRequest; - return proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.AttachPropertyValueResponse; + return proto.services.property_svc.v1.AttachPropertyValueResponse.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.AttachPropertyValueResponse} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} + * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.AttachPropertyValueResponse.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -1739,14 +1461,17 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinar var field = reader.getFieldNumber(); switch (field) { case 1: - var value = new proto.services.property_svc.v1.TaskPropertyMatcher; - reader.readMessage(value,proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader); - msg.setTaskMatcher(value); + var value = /** @type {string} */ (reader.readString()); + msg.setPropertyValueId(value); break; case 2: - var value = new proto.services.property_svc.v1.PatientPropertyMatcher; - reader.readMessage(value,proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinaryFromReader); - msg.setPatientMatcher(value); + var value = new libs_common_v1_conflict_pb.Conflict; + reader.readMessage(value,libs_common_v1_conflict_pb.Conflict.deserializeBinaryFromReader); + msg.setConflict(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setConsistency(value); break; default: reader.skipField(); @@ -1761,9 +1486,9 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinar * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.serializeBinary = function() { +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.AttachPropertyValueResponse.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1771,93 +1496,80 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.serial /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} message + * @param {!proto.services.property_svc.v1.AttachPropertyValueResponse} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.AttachPropertyValueResponse.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getTaskMatcher(); - if (f != null) { - writer.writeMessage( + f = message.getPropertyValueId(); + if (f.length > 0) { + writer.writeString( 1, - f, - proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter + f ); } - f = message.getPatientMatcher(); + f = message.getConflict(); if (f != null) { writer.writeMessage( 2, f, - proto.services.property_svc.v1.PatientPropertyMatcher.serializeBinaryToWriter + libs_common_v1_conflict_pb.Conflict.serializeBinaryToWriter + ); + } + f = message.getConsistency(); + if (f.length > 0) { + writer.writeString( + 3, + f ); } }; /** - * optional TaskPropertyMatcher task_matcher = 1; - * @return {?proto.services.property_svc.v1.TaskPropertyMatcher} - */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.getTaskMatcher = function() { - return /** @type{?proto.services.property_svc.v1.TaskPropertyMatcher} */ ( - jspb.Message.getWrapperField(this, proto.services.property_svc.v1.TaskPropertyMatcher, 1)); -}; - - -/** - * @param {?proto.services.property_svc.v1.TaskPropertyMatcher|undefined} value - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this -*/ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.setTaskMatcher = function(value) { - return jspb.Message.setOneofWrapperField(this, 1, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_[0], value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this + * optional string property_value_id = 1; + * @return {string} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.clearTaskMatcher = function() { - return this.setTaskMatcher(undefined); +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.getPropertyValueId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** - * Returns whether this field is set. - * @return {boolean} + * @param {string} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.hasTaskMatcher = function() { - return jspb.Message.getField(this, 1) != null; +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.setPropertyValueId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); }; /** - * optional PatientPropertyMatcher patient_matcher = 2; - * @return {?proto.services.property_svc.v1.PatientPropertyMatcher} + * optional libs.common.v1.Conflict conflict = 2; + * @return {?proto.libs.common.v1.Conflict} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.getPatientMatcher = function() { - return /** @type{?proto.services.property_svc.v1.PatientPropertyMatcher} */ ( - jspb.Message.getWrapperField(this, proto.services.property_svc.v1.PatientPropertyMatcher, 2)); +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.getConflict = function() { + return /** @type{?proto.libs.common.v1.Conflict} */ ( + jspb.Message.getWrapperField(this, libs_common_v1_conflict_pb.Conflict, 2)); }; /** - * @param {?proto.services.property_svc.v1.PatientPropertyMatcher|undefined} value - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this + * @param {?proto.libs.common.v1.Conflict|undefined} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.setPatientMatcher = function(value) { - return jspb.Message.setOneofWrapperField(this, 2, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_[0], value); +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.setConflict = function(value) { + return jspb.Message.setWrapperField(this, 2, value); }; /** * Clears the message field making it undefined. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this + * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.clearPatientMatcher = function() { - return this.setPatientMatcher(undefined); +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.clearConflict = function() { + return this.setConflict(undefined); }; @@ -1865,18 +1577,29 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.clearP * Returns whether this field is set. * @return {boolean} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.hasPatientMatcher = function() { +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.hasConflict = function() { return jspb.Message.getField(this, 2) != null; }; +/** + * optional string consistency = 3; + * @return {string} + */ +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.getConsistency = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + /** - * List of repeated fields within this message type. - * @private {!Array} - * @const + * @param {string} value + * @return {!proto.services.property_svc.v1.AttachPropertyValueResponse} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.repeatedFields_ = [1]; +proto.services.property_svc.v1.AttachPropertyValueResponse.prototype.setConsistency = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + @@ -1893,8 +1616,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.TaskPropertyMatcher.toObject(opt_includeInstance, this); }; @@ -1903,14 +1626,14 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.prototype.toObj * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.TaskPropertyMatcher} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.TaskPropertyMatcher.toObject = function(includeInstance, msg) { var f, obj = { - valuesList: jspb.Message.toObjectList(msg.getValuesList(), - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject, includeInstance) + wardId: jspb.Message.getFieldWithDefault(msg, 1, ""), + taskId: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -1924,23 +1647,23 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.toObject = func /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} + * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse; - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.TaskPropertyMatcher; + return proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.TaskPropertyMatcher} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} + * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -1948,9 +1671,12 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBina var field = reader.getFieldNumber(); switch (field) { case 1: - var value = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value; - reader.readMessage(value,proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinaryFromReader); - msg.addValues(value); + var value = /** @type {string} */ (reader.readString()); + msg.setWardId(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setTaskId(value); break; default: reader.skipField(); @@ -1965,9 +1691,9 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBina * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.prototype.serializeBinary = function() { +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1975,57 +1701,104 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.prototype.seria /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} message + * @param {!proto.services.property_svc.v1.TaskPropertyMatcher} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getValuesList(); - if (f.length > 0) { - writer.writeRepeatedMessage( + f = /** @type {string} */ (jspb.Message.getField(message, 1)); + if (f != null) { + writer.writeString( 1, - f, - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.serializeBinaryToWriter + f + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 2)); + if (f != null) { + writer.writeString( + 2, + f ); } }; +/** + * optional string ward_id = 1; + * @return {string} + */ +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.getWardId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + /** - * Oneof group definitions for this message. Each group defines the field - * numbers belonging to that group. When of these fields' value is set, all - * other fields in the group are cleared. During deserialization, if multiple - * fields are encountered for a group, only the last value seen will be kept. - * @private {!Array>} - * @const + * @param {string} value + * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.oneofGroups_ = [[6,7,8,9,10,11,12]]; +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.setWardId = function(value) { + return jspb.Message.setField(this, 1, value); +}; + /** - * @enum {number} + * Clears the field making it undefined. + * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase = { - VALUE_NOT_SET: 0, - TEXT_VALUE: 6, - NUMBER_VALUE: 7, - BOOL_VALUE: 8, - DATE_VALUE: 9, - DATE_TIME_VALUE: 10, - SELECT_VALUE: 11, - MULTI_SELECT_VALUE: 12 +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.clearWardId = function() { + return jspb.Message.setField(this, 1, undefined); }; + /** - * @return {proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase} + * Returns whether this field is set. + * @return {boolean} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.getValueCase = function() { - return /** @type {proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase} */(jspb.Message.computeOneofCase(this, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.oneofGroups_[0])); +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.hasWardId = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string task_id = 2; + * @return {string} + */ +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.getTaskId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this + */ +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.setTaskId = function(value) { + return jspb.Message.setField(this, 2, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.property_svc.v1.TaskPropertyMatcher} returns this + */ +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.clearTaskId = function() { + return jspb.Message.setField(this, 2, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.property_svc.v1.TaskPropertyMatcher.prototype.hasTaskId = function() { + return jspb.Message.getField(this, 2) != null; }; + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. @@ -2039,8 +1812,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.PatientPropertyMatcher.toObject(opt_includeInstance, this); }; @@ -2049,26 +1822,14 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.PatientPropertyMatcher} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.PatientPropertyMatcher.toObject = function(includeInstance, msg) { var f, obj = { - propertyId: jspb.Message.getFieldWithDefault(msg, 1, ""), - fieldType: jspb.Message.getFieldWithDefault(msg, 2, 0), - name: jspb.Message.getFieldWithDefault(msg, 3, ""), - description: jspb.Message.getFieldWithDefault(msg, 4, ""), - isArchived: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), - textValue: jspb.Message.getFieldWithDefault(msg, 6, ""), - numberValue: jspb.Message.getFloatingPointFieldWithDefault(msg, 7, 0.0), - boolValue: jspb.Message.getBooleanFieldWithDefault(msg, 8, false), - dateValue: (f = msg.getDateValue()) && services_property_svc_v1_types_pb.Date.toObject(includeInstance, f), - dateTimeValue: (f = msg.getDateTimeValue()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f), - selectValue: (f = msg.getSelectValue()) && proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.toObject(includeInstance, f), - multiSelectValue: (f = msg.getMultiSelectValue()) && proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.toObject(includeInstance, f), - propertyConsistency: jspb.Message.getFieldWithDefault(msg, 13, ""), - valueConsistency: jspb.Message.getFieldWithDefault(msg, 14, "") + wardId: jspb.Message.getFieldWithDefault(msg, 1, ""), + patientId: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -2082,23 +1843,23 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} + * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value; - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.PatientPropertyMatcher; + return proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.PatientPropertyMatcher} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} + * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -2107,63 +1868,11 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deseriali switch (field) { case 1: var value = /** @type {string} */ (reader.readString()); - msg.setPropertyId(value); + msg.setWardId(value); break; case 2: - var value = /** @type {!proto.services.property_svc.v1.FieldType} */ (reader.readEnum()); - msg.setFieldType(value); - break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setName(value); - break; - case 4: - var value = /** @type {string} */ (reader.readString()); - msg.setDescription(value); - break; - case 5: - var value = /** @type {boolean} */ (reader.readBool()); - msg.setIsArchived(value); - break; - case 6: - var value = /** @type {string} */ (reader.readString()); - msg.setTextValue(value); - break; - case 7: - var value = /** @type {number} */ (reader.readDouble()); - msg.setNumberValue(value); - break; - case 8: - var value = /** @type {boolean} */ (reader.readBool()); - msg.setBoolValue(value); - break; - case 9: - var value = new services_property_svc_v1_types_pb.Date; - reader.readMessage(value,services_property_svc_v1_types_pb.Date.deserializeBinaryFromReader); - msg.setDateValue(value); - break; - case 10: - var value = new google_protobuf_timestamp_pb.Timestamp; - reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader); - msg.setDateTimeValue(value); - break; - case 11: - var value = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption; - reader.readMessage(value,proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.deserializeBinaryFromReader); - msg.setSelectValue(value); - break; - case 12: - var value = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue; - reader.readMessage(value,proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.deserializeBinaryFromReader); - msg.setMultiSelectValue(value); - break; - case 13: var value = /** @type {string} */ (reader.readString()); - msg.setPropertyConsistency(value); - break; - case 14: - var value = /** @type {string} */ (reader.readString()); - msg.setValueConsistency(value); + msg.setPatientId(value); break; default: reader.skipField(); @@ -2178,9 +1887,9 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deseriali * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.serializeBinary = function() { +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.PatientPropertyMatcher.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -2188,117 +1897,127 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} message + * @param {!proto.services.property_svc.v1.PatientPropertyMatcher} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.PatientPropertyMatcher.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getPropertyId(); - if (f.length > 0) { - writer.writeString( - 1, - f - ); - } - f = message.getFieldType(); - if (f !== 0.0) { - writer.writeEnum( - 2, - f - ); - } - f = message.getName(); - if (f.length > 0) { - writer.writeString( - 3, - f - ); - } - f = /** @type {string} */ (jspb.Message.getField(message, 4)); - if (f != null) { - writer.writeString( - 4, - f - ); - } - f = message.getIsArchived(); - if (f) { - writer.writeBool( - 5, - f - ); - } - f = /** @type {string} */ (jspb.Message.getField(message, 6)); - if (f != null) { - writer.writeString( - 6, - f - ); - } - f = /** @type {number} */ (jspb.Message.getField(message, 7)); - if (f != null) { - writer.writeDouble( - 7, - f - ); - } - f = /** @type {boolean} */ (jspb.Message.getField(message, 8)); - if (f != null) { - writer.writeBool( - 8, - f - ); - } - f = message.getDateValue(); - if (f != null) { - writer.writeMessage( - 9, - f, - services_property_svc_v1_types_pb.Date.serializeBinaryToWriter - ); - } - f = message.getDateTimeValue(); - if (f != null) { - writer.writeMessage( - 10, - f, - google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter - ); - } - f = message.getSelectValue(); - if (f != null) { - writer.writeMessage( - 11, - f, - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.serializeBinaryToWriter - ); - } - f = message.getMultiSelectValue(); + f = /** @type {string} */ (jspb.Message.getField(message, 1)); if (f != null) { - writer.writeMessage( - 12, - f, - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.serializeBinaryToWriter - ); - } - f = message.getPropertyConsistency(); - if (f.length > 0) { writer.writeString( - 13, + 1, f ); } - f = /** @type {string} */ (jspb.Message.getField(message, 14)); + f = /** @type {string} */ (jspb.Message.getField(message, 2)); if (f != null) { writer.writeString( - 14, + 2, f ); } }; +/** + * optional string ward_id = 1; + * @return {string} + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.getWardId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.setWardId = function(value) { + return jspb.Message.setField(this, 1, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.clearWardId = function() { + return jspb.Message.setField(this, 1, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.hasWardId = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string patient_id = 2; + * @return {string} + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.getPatientId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.setPatientId = function(value) { + return jspb.Message.setField(this, 2, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.property_svc.v1.PatientPropertyMatcher} returns this + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.clearPatientId = function() { + return jspb.Message.setField(this, 2, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.property_svc.v1.PatientPropertyMatcher.prototype.hasPatientId = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_ = [[1,2]]; + +/** + * @enum {number} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase = { + MATCHER_NOT_SET: 0, + TASK_MATCHER: 1, + PATIENT_MATCHER: 2 +}; + +/** + * @return {proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.getMatcherCase = function() { + return /** @type {proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.MatcherCase} */(jspb.Message.computeOneofCase(this, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_[0])); +}; @@ -2315,8 +2034,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.toObject(opt_includeInstance, this); }; @@ -2325,15 +2044,14 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectVal * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.toObject = function(includeInstance, msg) { var f, obj = { - id: jspb.Message.getFieldWithDefault(msg, 1, ""), - name: jspb.Message.getFieldWithDefault(msg, 2, ""), - description: jspb.Message.getFieldWithDefault(msg, 3, "") + taskMatcher: (f = msg.getTaskMatcher()) && proto.services.property_svc.v1.TaskPropertyMatcher.toObject(includeInstance, f), + patientMatcher: (f = msg.getPatientMatcher()) && proto.services.property_svc.v1.PatientPropertyMatcher.toObject(includeInstance, f) }; if (includeInstance) { @@ -2347,23 +2065,23 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectVal /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption; - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesRequest; + return proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -2371,16 +2089,14 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectVal var field = reader.getFieldNumber(); switch (field) { case 1: - var value = /** @type {string} */ (reader.readString()); - msg.setId(value); + var value = new proto.services.property_svc.v1.TaskPropertyMatcher; + reader.readMessage(value,proto.services.property_svc.v1.TaskPropertyMatcher.deserializeBinaryFromReader); + msg.setTaskMatcher(value); break; case 2: - var value = /** @type {string} */ (reader.readString()); - msg.setName(value); - break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setDescription(value); + var value = new proto.services.property_svc.v1.PatientPropertyMatcher; + reader.readMessage(value,proto.services.property_svc.v1.PatientPropertyMatcher.deserializeBinaryFromReader); + msg.setPatientMatcher(value); break; default: reader.skipField(); @@ -2395,9 +2111,9 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectVal * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.serializeBinary = function() { +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -2405,97 +2121,258 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectVal /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} message + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getId(); - if (f.length > 0) { - writer.writeString( + f = message.getTaskMatcher(); + if (f != null) { + writer.writeMessage( 1, - f + f, + proto.services.property_svc.v1.TaskPropertyMatcher.serializeBinaryToWriter ); } - f = message.getName(); - if (f.length > 0) { - writer.writeString( + f = message.getPatientMatcher(); + if (f != null) { + writer.writeMessage( 2, - f + f, + proto.services.property_svc.v1.PatientPropertyMatcher.serializeBinaryToWriter ); } - f = message.getDescription(); - if (f.length > 0) { - writer.writeString( - 3, - f - ); +}; + + +/** + * optional TaskPropertyMatcher task_matcher = 1; + * @return {?proto.services.property_svc.v1.TaskPropertyMatcher} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.getTaskMatcher = function() { + return /** @type{?proto.services.property_svc.v1.TaskPropertyMatcher} */ ( + jspb.Message.getWrapperField(this, proto.services.property_svc.v1.TaskPropertyMatcher, 1)); +}; + + +/** + * @param {?proto.services.property_svc.v1.TaskPropertyMatcher|undefined} value + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this +*/ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.setTaskMatcher = function(value) { + return jspb.Message.setOneofWrapperField(this, 1, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.clearTaskMatcher = function() { + return this.setTaskMatcher(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.hasTaskMatcher = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional PatientPropertyMatcher patient_matcher = 2; + * @return {?proto.services.property_svc.v1.PatientPropertyMatcher} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.getPatientMatcher = function() { + return /** @type{?proto.services.property_svc.v1.PatientPropertyMatcher} */ ( + jspb.Message.getWrapperField(this, proto.services.property_svc.v1.PatientPropertyMatcher, 2)); +}; + + +/** + * @param {?proto.services.property_svc.v1.PatientPropertyMatcher|undefined} value + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this +*/ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.setPatientMatcher = function(value) { + return jspb.Message.setOneofWrapperField(this, 2, proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesRequest} returns this + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.clearPatientMatcher = function() { + return this.setPatientMatcher(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesRequest.prototype.hasPatientMatcher = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.toObject = function(includeInstance, msg) { + var f, obj = { + valuesList: jspb.Message.toObjectList(msg.getValuesList(), + proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject, includeInstance) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; } + return obj; }; +} /** - * optional string id = 1; - * @return {string} + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.getId = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse; + return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBinaryFromReader(msg, reader); }; /** - * @param {string} value - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} returns this + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.setId = function(value) { - return jspb.Message.setProto3StringField(this, 1, value); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value; + reader.readMessage(value,proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinaryFromReader); + msg.addValues(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; }; /** - * optional string name = 2; - * @return {string} + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.getName = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); }; /** - * @param {string} value - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} returns this + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.setName = function(value) { - return jspb.Message.setProto3StringField(this, 2, value); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getValuesList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 1, + f, + proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.serializeBinaryToWriter + ); + } }; + /** - * optional string description = 3; - * @return {string} + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.getDescription = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.oneofGroups_ = [[6,7,8,9,10,11,12]]; /** - * @param {string} value - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} returns this + * @enum {number} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.prototype.setDescription = function(value) { - return jspb.Message.setProto3StringField(this, 3, value); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase = { + VALUE_NOT_SET: 0, + TEXT_VALUE: 6, + NUMBER_VALUE: 7, + BOOL_VALUE: 8, + DATE_VALUE: 9, + DATE_TIME_VALUE: 10, + SELECT_VALUE: 11, + MULTI_SELECT_VALUE: 12 }; - - /** - * List of repeated fields within this message type. - * @private {!Array} - * @const + * @return {proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.repeatedFields_ = [1]; +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.getValueCase = function() { + return /** @type {proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.ValueCase} */(jspb.Message.computeOneofCase(this, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.oneofGroups_[0])); +}; @@ -2512,8 +2389,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.prototype.toObject = function(opt_includeInstance) { - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.toObject(opt_includeInstance, this); +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.toObject = function(opt_includeInstance) { + return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject(opt_includeInstance, this); }; @@ -2522,14 +2399,26 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSele * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} msg The msg instance to transform. + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.toObject = function(includeInstance, msg) { +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.toObject = function(includeInstance, msg) { var f, obj = { - selectValuesList: jspb.Message.toObjectList(msg.getSelectValuesList(), - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.toObject, includeInstance) + propertyId: jspb.Message.getFieldWithDefault(msg, 1, ""), + fieldType: jspb.Message.getFieldWithDefault(msg, 2, 0), + name: jspb.Message.getFieldWithDefault(msg, 3, ""), + description: jspb.Message.getFieldWithDefault(msg, 4, ""), + isArchived: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), + textValue: jspb.Message.getFieldWithDefault(msg, 6, ""), + numberValue: jspb.Message.getFloatingPointFieldWithDefault(msg, 7, 0.0), + boolValue: jspb.Message.getBooleanFieldWithDefault(msg, 8, false), + dateValue: (f = msg.getDateValue()) && services_property_svc_v1_types_pb.Date.toObject(includeInstance, f), + dateTimeValue: (f = msg.getDateTimeValue()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f), + selectValue: (f = msg.getSelectValue()) && proto.services.property_svc.v1.SelectValueOption.toObject(includeInstance, f), + multiSelectValue: (f = msg.getMultiSelectValue()) && proto.services.property_svc.v1.MultiSelectValue.toObject(includeInstance, f), + propertyConsistency: jspb.Message.getFieldWithDefault(msg, 13, ""), + valueConsistency: jspb.Message.getFieldWithDefault(msg, 14, "") }; if (includeInstance) { @@ -2543,23 +2432,23 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSele /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.deserializeBinary = function(bytes) { +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue; - return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.deserializeBinaryFromReader(msg, reader); + var msg = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value; + return proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} msg The message object to deserialize into. + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} + * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.deserializeBinaryFromReader = function(msg, reader) { +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -2567,9 +2456,64 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSele var field = reader.getFieldNumber(); switch (field) { case 1: - var value = new proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption; - reader.readMessage(value,proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.deserializeBinaryFromReader); - msg.addSelectValues(value); + var value = /** @type {string} */ (reader.readString()); + msg.setPropertyId(value); + break; + case 2: + var value = /** @type {!proto.services.property_svc.v1.FieldType} */ (reader.readEnum()); + msg.setFieldType(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setDescription(value); + break; + case 5: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setIsArchived(value); + break; + case 6: + var value = /** @type {string} */ (reader.readString()); + msg.setTextValue(value); + break; + case 7: + var value = /** @type {number} */ (reader.readDouble()); + msg.setNumberValue(value); + break; + case 8: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setBoolValue(value); + break; + case 9: + var value = new services_property_svc_v1_types_pb.Date; + reader.readMessage(value,services_property_svc_v1_types_pb.Date.deserializeBinaryFromReader); + msg.setDateValue(value); + break; + case 10: + var value = new google_protobuf_timestamp_pb.Timestamp; + reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader); + msg.setDateTimeValue(value); + break; + case 11: + var value = new proto.services.property_svc.v1.SelectValueOption; + reader.readMessage(value,proto.services.property_svc.v1.SelectValueOption.deserializeBinaryFromReader); + msg.setSelectValue(value); + break; + case 12: + var value = new proto.services.property_svc.v1.MultiSelectValue; + reader.readMessage(value,proto.services.property_svc.v1.MultiSelectValue.deserializeBinaryFromReader); + msg.setMultiSelectValue(value); + break; + case 13: + var value = /** @type {string} */ (reader.readString()); + msg.setPropertyConsistency(value); + break; + case 14: + var value = /** @type {string} */ (reader.readString()); + msg.setValueConsistency(value); break; default: reader.skipField(); @@ -2584,9 +2528,9 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSele * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.prototype.serializeBinary = function() { +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.serializeBinaryToWriter(this, writer); + proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -2594,58 +2538,114 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSele /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} message + * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.serializeBinaryToWriter = function(message, writer) { +proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getSelectValuesList(); + f = message.getPropertyId(); if (f.length > 0) { - writer.writeRepeatedMessage( + writer.writeString( 1, + f + ); + } + f = message.getFieldType(); + if (f !== 0.0) { + writer.writeEnum( + 2, + f + ); + } + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 4)); + if (f != null) { + writer.writeString( + 4, + f + ); + } + f = message.getIsArchived(); + if (f) { + writer.writeBool( + 5, + f + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 6)); + if (f != null) { + writer.writeString( + 6, + f + ); + } + f = /** @type {number} */ (jspb.Message.getField(message, 7)); + if (f != null) { + writer.writeDouble( + 7, + f + ); + } + f = /** @type {boolean} */ (jspb.Message.getField(message, 8)); + if (f != null) { + writer.writeBool( + 8, + f + ); + } + f = message.getDateValue(); + if (f != null) { + writer.writeMessage( + 9, f, - proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption.serializeBinaryToWriter + services_property_svc_v1_types_pb.Date.serializeBinaryToWriter + ); + } + f = message.getDateTimeValue(); + if (f != null) { + writer.writeMessage( + 10, + f, + google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter + ); + } + f = message.getSelectValue(); + if (f != null) { + writer.writeMessage( + 11, + f, + proto.services.property_svc.v1.SelectValueOption.serializeBinaryToWriter + ); + } + f = message.getMultiSelectValue(); + if (f != null) { + writer.writeMessage( + 12, + f, + proto.services.property_svc.v1.MultiSelectValue.serializeBinaryToWriter + ); + } + f = message.getPropertyConsistency(); + if (f.length > 0) { + writer.writeString( + 13, + f + ); + } + f = /** @type {string} */ (jspb.Message.getField(message, 14)); + if (f != null) { + writer.writeString( + 14, + f ); } -}; - - -/** - * repeated SelectValueOption select_values = 1; - * @return {!Array} - */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.prototype.getSelectValuesList = function() { - return /** @type{!Array} */ ( - jspb.Message.getRepeatedWrapperField(this, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption, 1)); -}; - - -/** - * @param {!Array} value - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} returns this -*/ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.prototype.setSelectValuesList = function(value) { - return jspb.Message.setRepeatedWrapperField(this, 1, value); -}; - - -/** - * @param {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption=} opt_value - * @param {number=} opt_index - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} - */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.prototype.addSelectValues = function(opt_value, opt_index) { - return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption, opt_index); -}; - - -/** - * Clears the list making it empty but non-null. - * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} returns this - */ -proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue.prototype.clearSelectValuesList = function() { - return this.setSelectValuesList([]); }; @@ -2941,16 +2941,16 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype /** * optional SelectValueOption select_value = 11; - * @return {?proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} + * @return {?proto.services.property_svc.v1.SelectValueOption} */ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.getSelectValue = function() { - return /** @type{?proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption} */ ( - jspb.Message.getWrapperField(this, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption, 11)); + return /** @type{?proto.services.property_svc.v1.SelectValueOption} */ ( + jspb.Message.getWrapperField(this, proto.services.property_svc.v1.SelectValueOption, 11)); }; /** - * @param {?proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.SelectValueOption|undefined} value + * @param {?proto.services.property_svc.v1.SelectValueOption|undefined} value * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} returns this */ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.setSelectValue = function(value) { @@ -2978,16 +2978,16 @@ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype /** * optional MultiSelectValue multi_select_value = 12; - * @return {?proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} + * @return {?proto.services.property_svc.v1.MultiSelectValue} */ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.getMultiSelectValue = function() { - return /** @type{?proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue} */ ( - jspb.Message.getWrapperField(this, proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue, 12)); + return /** @type{?proto.services.property_svc.v1.MultiSelectValue} */ ( + jspb.Message.getWrapperField(this, proto.services.property_svc.v1.MultiSelectValue, 12)); }; /** - * @param {?proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.MultiSelectValue|undefined} value + * @param {?proto.services.property_svc.v1.MultiSelectValue|undefined} value * @return {!proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value} returns this */ proto.services.property_svc.v1.GetAttachedPropertyValuesResponse.Value.prototype.setMultiSelectValue = function(value) { diff --git a/proto/libs/common/v1/conflict.proto b/proto/libs/common/v1/conflict.proto index d643eacd2..a2c423aa8 100644 --- a/proto/libs/common/v1/conflict.proto +++ b/proto/libs/common/v1/conflict.proto @@ -27,8 +27,16 @@ message Conflict { message AttributeConflict { // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds) // Enums are returned as Int32s + // Arrays are encoded as AnyArrays google.protobuf.Any is = 1; // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed) // Enums are returned as Int32s + // Arrays are encoded as AnyArrays google.protobuf.Any want = 2; } + +// there is no native Any-compatible wrapper for arrays, +// so here is one +message AnyArray { + repeated google.protobuf.Any elements = 1; +} diff --git a/proto/services/property_svc/v1/property_value_svc.proto b/proto/services/property_svc/v1/property_value_svc.proto index b48226412..4cfb0f1de 100644 --- a/proto/services/property_svc/v1/property_value_svc.proto +++ b/proto/services/property_svc/v1/property_value_svc.proto @@ -8,6 +8,16 @@ import "libs/common/v1/conflict.proto"; option go_package = "gen/services/property-svc/v1"; +message SelectValueOption { + string id = 1; + string name = 2; + string description = 3; +} + +message MultiSelectValue { + repeated SelectValueOption select_values = 1; +} + // // AttachPropertyValueRequest // @@ -93,15 +103,7 @@ message GetAttachedPropertyValuesResponse { string property_consistency = 13; optional string value_consistency = 14; // missing, if no value available - message SelectValueOption { - string id = 1; - string name = 2; - string description = 3; - } - message MultiSelectValue { - repeated SelectValueOption select_values = 1; - } } repeated Value values = 1; diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 49906cfea..4772797c1 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -1,8 +1,10 @@ package api import ( + "common" "context" "fmt" + commonpb "gen/libs/common/v1" pb "gen/services/property_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog/log" @@ -12,6 +14,7 @@ import ( "property-svc/internal/property-value/handlers" "property-svc/internal/property-value/models" viewModels "property-svc/internal/property-view/models" + "property-svc/util" ) type MatchersRequest interface { @@ -97,11 +100,36 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req value = nil } - consistency, err := s.handlers.Commands.V1.AttachPropertyValue(ctx, propertyValueID, propertyID, value, subjectID) + expConsistency, ok := common.ParseConsistency(req.Consistency) + if !ok { + return nil, common.UnparsableConsistencyError(ctx, "consistency") + } + + consistency, conflictIs, err := s.handlers.Commands.V1.AttachPropertyValue(ctx, propertyValueID, propertyID, value, subjectID, expConsistency) if err != nil { return nil, err } + if conflictIs != nil { + value, err := util.AttributeConflict( + conflictIs, + nil, // TODO + ) + if err != nil { + return nil, err + } + return &pb.AttachPropertyValueResponse{ + PropertyValueId: "", + Conflict: &commonpb.Conflict{ + ConflictingAttributes: map[string]*commonpb.AttributeConflict{ + "value": value, + }, + HistoryMissing: true, + }, + Consistency: consistency.String(), + }, nil + } + return &pb.AttachPropertyValueResponse{ PropertyValueId: propertyValueID.String(), Consistency: consistency.String(), diff --git a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go index b06d69cc1..e821865b3 100644 --- a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go +++ b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go @@ -3,43 +3,124 @@ package v1 import ( "common" "context" + commonpb "gen/libs/common/v1" + pb "gen/services/property_svc/v1" "github.com/google/uuid" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwes" + "hwutil" "property-svc/internal/property-value/aggregate" "property-svc/repos/property_value_repo" ) -type AttachPropertyValueCommandHandler func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID) (common.ConsistencyToken, error) +type AttachPropertyValueCommandHandler func(ctx context.Context, + propertyValueID uuid.UUID, + propertyID uuid.UUID, + value interface{}, + subjectID uuid.UUID, + expConsistency *common.ConsistencyToken, +) (common.ConsistencyToken, proto.Message, error) + +func protoMessagesToAnyMessages[T proto.Message](msgs []T) ([]*anypb.Any, error) { + return hwutil.MapWithErr(msgs, func(m T) (*anypb.Any, error) { + return anypb.New(m) + }) +} + +func propertyValueRowsToSelectOptions(rows []property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDRow) []*pb.SelectValueOption { + out := make([]*pb.SelectValueOption, 0, len(rows)) + + for _, option := range rows { + if option.SelectOptionID.Valid { + out = append(out, &pb.SelectValueOption{ + Id: option.SelectOptionID.UUID.String(), + Name: *option.SelectOptionName, // safe: NOT NULL + Description: *option.SelectOptionDescription, // safe: NOT NULL + }) + } + } + + return out +} + +func propertyValueRowsToMessage(rows []property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDRow) (proto.Message, error) { + // we get most information from the first row, the other rows are only relevant for multi-select options + firstRow := rows[0] // precondition: rows is not empty + + val := firstRow.PropertyValue + fieldType := pb.FieldType(firstRow.FieldType) + + switch { + case val.TextValue != nil: + return wrapperspb.String(*val.TextValue), nil + case val.NumberValue != nil: + return wrapperspb.Double(*val.NumberValue), nil + case val.BoolValue != nil: + return wrapperspb.Bool(*val.BoolValue), nil + case val.DateValue.Valid: + return &pb.Date{Date: timestamppb.New(val.DateValue.Time)}, nil + case val.DateTimeValue.Valid: + return timestamppb.New(val.DateTimeValue.Time), nil + case firstRow.SelectOptionID.Valid: + opts := propertyValueRowsToSelectOptions(rows) + + // single-select: only return first (only) option without array-wrapping + if fieldType == pb.FieldType_FIELD_TYPE_SELECT { + return opts[0], nil + } + // mutli select + arr, err := protoMessagesToAnyMessages(opts) + return &commonpb.AnyArray{Elements: arr}, err + default: + return nil, nil + } +} func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachPropertyValueCommandHandler { - return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID) (common.ConsistencyToken, error) { + return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, proto.Message, error) { propertyValueRepo := property_value_repo.New(hwdb.GetDB()) var a *aggregate.PropertyValueAggregate - existingPropertyValueID, err := hwdb.Optional(propertyValueRepo.GetPropertyValueBySubjectIDAndPropertyID)(ctx, property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDParams{ + rows, err := propertyValueRepo.GetPropertyValueBySubjectIDAndPropertyID(ctx, property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDParams{ PropertyID: propertyID, SubjectID: subjectID, }) if err := hwdb.Error(ctx, err); err != nil { - return 0, err + return 0, nil, err } - if existingPropertyValueID != nil { - if a, err = aggregate.LoadPropertyValueAggregate(ctx, as, *existingPropertyValueID); err != nil { - return 0, err + if len(rows) != 0 { + propertyValue := rows[0].PropertyValue + existingPropertyValueID := propertyValue.PropertyID + consistency := common.ConsistencyToken(propertyValue.Consistency) + + // conflict detection + if expConsistency != nil && consistency != *expConsistency { + conflict, err := propertyValueRowsToMessage(rows) + return consistency, conflict, err + } + + if a, err = aggregate.LoadPropertyValueAggregate(ctx, as, existingPropertyValueID); err != nil { + return 0, nil, err } + // TODO: update value will be triggered, even if the value is not the type the property defines + if err := a.UpdatePropertyValue(ctx, value); err != nil { - return 0, err + return 0, nil, err } } else { a = aggregate.NewPropertyValueAggregate(propertyValueID) if err := a.CreatePropertyValue(ctx, propertyID, value, subjectID); err != nil { - return 0, err + return 0, nil, err } } - return as.Save(ctx, a) + consistency, err := as.Save(ctx, a) + return consistency, nil, err } } diff --git a/services/property-svc/repos/property_value_repo.sql b/services/property-svc/repos/property_value_repo.sql index 5018068c6..9aaea314b 100644 --- a/services/property-svc/repos/property_value_repo.sql +++ b/services/property-svc/repos/property_value_repo.sql @@ -22,10 +22,18 @@ DELETE FROM multi_select_values WHERE value_id = $1 AND select_option = $2; -- name: DisconnectValueFromAllSelectOptions :exec DELETE FROM multi_select_values WHERE value_id = $1; --- name: GetPropertyValueBySubjectIDAndPropertyID :one -SELECT id -FROM property_values -WHERE (subject_id = @subject_id AND property_id = @property_id) OR id = @id; +-- name: GetPropertyValueBySubjectIDAndPropertyID :many +SELECT + sqlc.embed(values), + so.id as select_option_id, + so.name as select_option_name, + so.description as select_option_description, + properties.field_type as field_type +FROM property_values as values + JOIN properties ON properties.id = values.property_id + LEFT JOIN multi_select_values as msv ON msv.value_id = values.id + LEFT JOIN select_options as so ON so.id = msv.select_option +WHERE (subject_id = @subject_id AND property_id = @property_id); -- name: UpdatePropertyValueByID :exec UPDATE property_values diff --git a/services/property-svc/repos/property_value_repo/property_value_repo.sql.go b/services/property-svc/repos/property_value_repo/property_value_repo.sql.go index 3d80ad865..22b0289a1 100644 --- a/services/property-svc/repos/property_value_repo/property_value_repo.sql.go +++ b/services/property-svc/repos/property_value_repo/property_value_repo.sql.go @@ -94,23 +94,65 @@ func (q *Queries) GetPropertyValueByID(ctx context.Context, id uuid.UUID) (Prope return i, err } -const getPropertyValueBySubjectIDAndPropertyID = `-- name: GetPropertyValueBySubjectIDAndPropertyID :one -SELECT id -FROM property_values -WHERE (subject_id = $1 AND property_id = $2) OR id = $3 +const getPropertyValueBySubjectIDAndPropertyID = `-- name: GetPropertyValueBySubjectIDAndPropertyID :many +SELECT + values.id, values.property_id, values.subject_id, values.text_value, values.number_value, values.bool_value, values.date_value, values.date_time_value, values.consistency, + so.id as select_option_id, + so.name as select_option_name, + so.description as select_option_description, + properties.field_type as field_type +FROM property_values as values + JOIN properties ON properties.id = values.property_id + LEFT JOIN multi_select_values as msv ON msv.value_id = values.id + LEFT JOIN select_options as so ON so.id = msv.select_option +WHERE (subject_id = $1 AND property_id = $2) ` type GetPropertyValueBySubjectIDAndPropertyIDParams struct { SubjectID uuid.UUID PropertyID uuid.UUID - ID uuid.UUID } -func (q *Queries) GetPropertyValueBySubjectIDAndPropertyID(ctx context.Context, arg GetPropertyValueBySubjectIDAndPropertyIDParams) (uuid.UUID, error) { - row := q.db.QueryRow(ctx, getPropertyValueBySubjectIDAndPropertyID, arg.SubjectID, arg.PropertyID, arg.ID) - var id uuid.UUID - err := row.Scan(&id) - return id, err +type GetPropertyValueBySubjectIDAndPropertyIDRow struct { + PropertyValue PropertyValue + SelectOptionID uuid.NullUUID + SelectOptionName *string + SelectOptionDescription *string + FieldType int32 +} + +func (q *Queries) GetPropertyValueBySubjectIDAndPropertyID(ctx context.Context, arg GetPropertyValueBySubjectIDAndPropertyIDParams) ([]GetPropertyValueBySubjectIDAndPropertyIDRow, error) { + rows, err := q.db.Query(ctx, getPropertyValueBySubjectIDAndPropertyID, arg.SubjectID, arg.PropertyID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetPropertyValueBySubjectIDAndPropertyIDRow{} + for rows.Next() { + var i GetPropertyValueBySubjectIDAndPropertyIDRow + if err := rows.Scan( + &i.PropertyValue.ID, + &i.PropertyValue.PropertyID, + &i.PropertyValue.SubjectID, + &i.PropertyValue.TextValue, + &i.PropertyValue.NumberValue, + &i.PropertyValue.BoolValue, + &i.PropertyValue.DateValue, + &i.PropertyValue.DateTimeValue, + &i.PropertyValue.Consistency, + &i.SelectOptionID, + &i.SelectOptionName, + &i.SelectOptionDescription, + &i.FieldType, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil } const getPropertyValuesWithPropertyBySubjectID = `-- name: GetPropertyValuesWithPropertyBySubjectID :many From cb3cd84de958041c52998ab9044a2cb765b6827f Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 10 Oct 2024 14:36:01 +0200 Subject: [PATCH 18/42] add want to attachpropertyvalue --- .../property-svc/internal/property-value/api/grpc.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 4772797c1..fb15ad6b2 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -8,7 +8,9 @@ import ( pb "gen/services/property_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog/log" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "hwes" "hwutil" "property-svc/internal/property-value/handlers" @@ -76,26 +78,34 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req propertyID := uuid.MustParse(req.GetPropertyId()) // guarded by validate subjectID := uuid.MustParse(req.GetSubjectId()) // guarded by validate + var want proto.Message var value interface{} switch req.Value.(type) { case *pb.AttachPropertyValueRequest_TextValue: value = req.GetTextValue() + want = wrapperspb.String(req.GetTextValue()) case *pb.AttachPropertyValueRequest_NumberValue: value = req.GetNumberValue() + want = wrapperspb.Double(req.GetNumberValue()) case *pb.AttachPropertyValueRequest_BoolValue: value = req.GetBoolValue() + want = wrapperspb.Bool(req.GetBoolValue()) case *pb.AttachPropertyValueRequest_DateValue: value = req.GetDateValue().Date + want = &pb.Date{Date: req.GetDateTimeValue()} case *pb.AttachPropertyValueRequest_DateTimeValue: value = req.GetDateTimeValue() + want = req.GetDateTimeValue() case *pb.AttachPropertyValueRequest_SelectValue: value = req.GetSelectValue() + want = wrapperspb.String(req.GetSelectValue()) // IS will be a pb.SelectValueOption, not a str, might be confusing for the frontend, but I'm not going to query the db for this case *pb.AttachPropertyValueRequest_MultiSelectValue_: msv := req.GetMultiSelectValue() value = models.MultiSelectChange{ SelectValues: msv.SelectValues, RemoveSelectValues: msv.RemoveSelectValues, } + want = req.GetMultiSelectValue() default: value = nil } @@ -113,7 +123,7 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req if conflictIs != nil { value, err := util.AttributeConflict( conflictIs, - nil, // TODO + want, ) if err != nil { return nil, err From d2ad8a6db8b908db2d96a51e7380408cc7eb8d52 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Thu, 10 Oct 2024 14:38:55 +0200 Subject: [PATCH 19/42] fix proto issues --- .../property-svc/internal/property-value/api/grpc.go | 10 +++++----- services/property-svc/stories/setup_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index fb15ad6b2..8238de949 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -190,17 +190,17 @@ func (s *PropertyValueGrpcService) GetAttachedPropertyValues(ctx context.Context case len(pnv.Value.MultiSelectValues) != 0 && pnv.FieldType == pb.FieldType_FIELD_TYPE_SELECT: v := pnv.Value.MultiSelectValues[0] res.Value = &pb.GetAttachedPropertyValuesResponse_Value_SelectValue{ - SelectValue: &pb.GetAttachedPropertyValuesResponse_Value_SelectValueOption{ + SelectValue: &pb.SelectValueOption{ Id: v.Id.String(), Name: v.Name, Description: v.Description, }, } case len(pnv.Value.MultiSelectValues) != 0 && pnv.FieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT: - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_MultiSelectValue_{ - MultiSelectValue: &pb.GetAttachedPropertyValuesResponse_Value_MultiSelectValue{ - SelectValues: hwutil.Map(pnv.Value.MultiSelectValues, func(o models.SelectValueOption) *pb.GetAttachedPropertyValuesResponse_Value_SelectValueOption { - return &pb.GetAttachedPropertyValuesResponse_Value_SelectValueOption{ + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_MultiSelectValue{ + MultiSelectValue: &pb.MultiSelectValue{ + SelectValues: hwutil.Map(pnv.Value.MultiSelectValues, func(o models.SelectValueOption) *pb.SelectValueOption { + return &pb.SelectValueOption{ Id: o.Id.String(), Name: o.Name, Description: o.Description, diff --git a/services/property-svc/stories/setup_test.go b/services/property-svc/stories/setup_test.go index 77a41a5e4..f9545da6d 100644 --- a/services/property-svc/stories/setup_test.go +++ b/services/property-svc/stories/setup_test.go @@ -68,8 +68,8 @@ func propertyValueServiceClient() pb.PropertyValueServiceClient { return pb.NewPropertyValueServiceClient(hwtesting.GetGrpcConn("")) } -func NamesOf(arr []*pb.GetAttachedPropertyValuesResponse_Value_SelectValueOption) []string { - strs := hwutil.Map(arr, func(v *pb.GetAttachedPropertyValuesResponse_Value_SelectValueOption) string { +func NamesOf(arr []*pb.SelectValueOption) []string { + strs := hwutil.Map(arr, func(v *pb.SelectValueOption) string { return v.Name }) sort.Strings(strs) From 279353fad033f68885ace13197c2e882e36c46db Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 11 Oct 2024 09:45:21 +0200 Subject: [PATCH 20/42] I genuinely love tests --- .../property-value/commands/v1/attach_property_value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go index e821865b3..0db9e63f9 100644 --- a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go +++ b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go @@ -95,7 +95,7 @@ func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachProperty if len(rows) != 0 { propertyValue := rows[0].PropertyValue - existingPropertyValueID := propertyValue.PropertyID + existingPropertyValueID := propertyValue.ID consistency := common.ConsistencyToken(propertyValue.Consistency) // conflict detection From 052dd8573865f7998069a0e6ec8c735a669c64f0 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 11 Oct 2024 10:00:24 +0200 Subject: [PATCH 21/42] text conflict test --- .../AttachPropertyValueConflict_test.go | 81 +++++++++++++++++++ .../stories/UpdatePropertyConflict_test.go | 1 - 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 services/property-svc/stories/AttachPropertyValueConflict_test.go diff --git a/services/property-svc/stories/AttachPropertyValueConflict_test.go b/services/property-svc/stories/AttachPropertyValueConflict_test.go new file mode 100644 index 000000000..4e85e17fd --- /dev/null +++ b/services/property-svc/stories/AttachPropertyValueConflict_test.go @@ -0,0 +1,81 @@ +package stories + +import ( + "context" + pb "gen/services/property_svc/v1" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "hwtesting" + "strings" + "testing" +) + +func TestAttachPropertyValueConflict(t *testing.T) { + propertyClient := propertyServiceClient() + propertyValueClient := propertyValueServiceClient() + + ctx := context.Background() + + // Preparations + + createPropertyRequest := &pb.CreatePropertyRequest{ + SubjectType: pb.SubjectType_SUBJECT_TYPE_PATIENT, + FieldType: pb.FieldType_FIELD_TYPE_TEXT, + Name: t.Name(), + Description: nil, + SetId: nil, + } + + createRes, err := propertyClient.CreateProperty(ctx, createPropertyRequest) + assert.NoError(t, err) + hwtesting.WaitForProjectionsToSettle() + + propertyID := createRes.PropertyId + subjectID := uuid.New().String() + + // WAS + + wasRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_TextValue{ + TextValue: t.Name() + " WAS", + }, + Consistency: nil, + }) + + assert.NoError(t, err) + assert.Nil(t, wasRes.Conflict) + + initialConsistency := wasRes.Consistency + + // IS + + isRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_TextValue{ + TextValue: t.Name() + " IS", + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, isRes.Conflict) + + // WANT + + wantRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_TextValue{ + TextValue: t.Name() + " WANT", + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.NotNil(t, wantRes.Conflict) + + // TODO + assert.Equal(t, "TODO", strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) + +} diff --git a/services/property-svc/stories/UpdatePropertyConflict_test.go b/services/property-svc/stories/UpdatePropertyConflict_test.go index bc190c18a..9388a3e13 100644 --- a/services/property-svc/stories/UpdatePropertyConflict_test.go +++ b/services/property-svc/stories/UpdatePropertyConflict_test.go @@ -132,5 +132,4 @@ func TestUpdatePropertyConflict(t *testing.T) { ) } - // TODO } From 4f82f0705f971f251a33121f1e31b421e95b1c72 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 11 Oct 2024 10:39:19 +0200 Subject: [PATCH 22/42] wtf was I thinking yesterday? --- .../property-value/aggregate/aggregate.go | 25 ++++++ .../internal/property-value/api/grpc.go | 58 ++++++++---- .../commands/v1/attach_property_value.go | 88 ++++--------------- 3 files changed, 81 insertions(+), 90 deletions(-) diff --git a/services/property-svc/internal/property-value/aggregate/aggregate.go b/services/property-svc/internal/property-value/aggregate/aggregate.go index b18e07db0..74113b3ca 100644 --- a/services/property-svc/internal/property-value/aggregate/aggregate.go +++ b/services/property-svc/internal/property-value/aggregate/aggregate.go @@ -1,6 +1,7 @@ package aggregate import ( + "common" "context" "fmt" "github.com/google/uuid" @@ -32,6 +33,30 @@ func LoadPropertyValueAggregate(ctx context.Context, as hwes.AggregateStore, id return property, nil } +func LoadPropertyValueAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore, id uuid.UUID, pauseAt *common.ConsistencyToken) (*PropertyValueAggregate, *models.PropertyValue, error) { + property := NewPropertyValueAggregate(id) + + var snapshot *models.PropertyValue + + if pauseAt != nil { + // load pauseAt+1-many events (version is 0-indexed) + if err := as.LoadN(ctx, property, uint64(*pauseAt)+1); err != nil { + return nil, nil, err + } + + cpy := *property.PropertyValue // deref does a top-level copy + // well see how it deals with .Value, of type interface (TODO) + + snapshot = &cpy + } + + // load the rest + if err := as.Load(ctx, property); err != nil { + return nil, nil, fmt.Errorf("LoadPropertyValueAggregateWithSnapshotAt: %w", err) + } + return property, snapshot, nil +} + func (a *PropertyValueAggregate) initEventListeners() { a.RegisterEventListener(propertyEventsV1.PropertyValueCreated, a.onPropertyValueCreated) a.RegisterEventListener(propertyEventsV1.PropertyValueUpdated, a.onPropertyValueUpdated) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 8238de949..fd407d518 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -73,6 +73,7 @@ func NewPropertyValueService(aggregateStore hwes.AggregateStore, handlers *handl } func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req *pb.AttachPropertyValueRequest) (*pb.AttachPropertyValueResponse, error) { + log := zlog.Ctx(ctx) propertyValueID := uuid.New() propertyID := uuid.MustParse(req.GetPropertyId()) // guarded by validate @@ -115,29 +116,48 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req return nil, common.UnparsableConsistencyError(ctx, "consistency") } - consistency, conflictIs, err := s.handlers.Commands.V1.AttachPropertyValue(ctx, propertyValueID, propertyID, value, subjectID, expConsistency) - if err != nil { - return nil, err - } + var consistency common.ConsistencyToken - if conflictIs != nil { - value, err := util.AttributeConflict( - conflictIs, - want, - ) + for i := 0; true; i++ { + if i > 10 { + log.Warn().Msg("AttachPropertyValue: conflict circuit breaker triggered") + return nil, fmt.Errorf("failed conflict resolution") + } + + c, conflict, err := s.handlers.Commands.V1.AttachPropertyValue(ctx, propertyValueID, propertyID, value, subjectID, expConsistency) if err != nil { return nil, err } - return &pb.AttachPropertyValueResponse{ - PropertyValueId: "", - Conflict: &commonpb.Conflict{ - ConflictingAttributes: map[string]*commonpb.AttributeConflict{ - "value": value, - }, - HistoryMissing: true, - }, - Consistency: consistency.String(), - }, nil + consistency = c + + if conflict == nil { + break + } + conflicts := make(map[string]*commonpb.AttributeConflict) + + // TODO: find a generic approach + valueUpdateRequested := req.Value != conflict.Is.Value // TODO + valueAlreadyUpdated := conflict.Was.Value != conflict.Is.Value // same TODO + if valueUpdateRequested && valueAlreadyUpdated { + var is proto.Message = nil // TODO + conflicts["value"], err = util.AttributeConflict( + is, + want, + ) + if err != nil { + return nil, err + } + } + + if len(conflicts) != 0 { + return &pb.AttachPropertyValueResponse{ + Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, + Consistency: consistency.String(), + }, nil + } + + // no conflict? retry with new consistency + expConsistency = &consistency } return &pb.AttachPropertyValueResponse{ diff --git a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go index 0db9e63f9..605fbbd09 100644 --- a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go +++ b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go @@ -3,17 +3,11 @@ package v1 import ( "common" "context" - commonpb "gen/libs/common/v1" - pb "gen/services/property_svc/v1" "github.com/google/uuid" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwes" - "hwutil" "property-svc/internal/property-value/aggregate" + "property-svc/internal/property-value/models" "property-svc/repos/property_value_repo" ) @@ -23,65 +17,15 @@ type AttachPropertyValueCommandHandler func(ctx context.Context, value interface{}, subjectID uuid.UUID, expConsistency *common.ConsistencyToken, -) (common.ConsistencyToken, proto.Message, error) +) (common.ConsistencyToken, *AttachPropertyValueConflict, error) -func protoMessagesToAnyMessages[T proto.Message](msgs []T) ([]*anypb.Any, error) { - return hwutil.MapWithErr(msgs, func(m T) (*anypb.Any, error) { - return anypb.New(m) - }) -} - -func propertyValueRowsToSelectOptions(rows []property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDRow) []*pb.SelectValueOption { - out := make([]*pb.SelectValueOption, 0, len(rows)) - - for _, option := range rows { - if option.SelectOptionID.Valid { - out = append(out, &pb.SelectValueOption{ - Id: option.SelectOptionID.UUID.String(), - Name: *option.SelectOptionName, // safe: NOT NULL - Description: *option.SelectOptionDescription, // safe: NOT NULL - }) - } - } - - return out -} - -func propertyValueRowsToMessage(rows []property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDRow) (proto.Message, error) { - // we get most information from the first row, the other rows are only relevant for multi-select options - firstRow := rows[0] // precondition: rows is not empty - - val := firstRow.PropertyValue - fieldType := pb.FieldType(firstRow.FieldType) - - switch { - case val.TextValue != nil: - return wrapperspb.String(*val.TextValue), nil - case val.NumberValue != nil: - return wrapperspb.Double(*val.NumberValue), nil - case val.BoolValue != nil: - return wrapperspb.Bool(*val.BoolValue), nil - case val.DateValue.Valid: - return &pb.Date{Date: timestamppb.New(val.DateValue.Time)}, nil - case val.DateTimeValue.Valid: - return timestamppb.New(val.DateTimeValue.Time), nil - case firstRow.SelectOptionID.Valid: - opts := propertyValueRowsToSelectOptions(rows) - - // single-select: only return first (only) option without array-wrapping - if fieldType == pb.FieldType_FIELD_TYPE_SELECT { - return opts[0], nil - } - // mutli select - arr, err := protoMessagesToAnyMessages(opts) - return &commonpb.AnyArray{Elements: arr}, err - default: - return nil, nil - } +type AttachPropertyValueConflict struct { + Was *models.PropertyValue + Is *models.PropertyValue } func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachPropertyValueCommandHandler { - return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, proto.Message, error) { + return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AttachPropertyValueConflict, error) { propertyValueRepo := property_value_repo.New(hwdb.GetDB()) var a *aggregate.PropertyValueAggregate @@ -94,18 +38,20 @@ func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachProperty } if len(rows) != 0 { - propertyValue := rows[0].PropertyValue - existingPropertyValueID := propertyValue.ID - consistency := common.ConsistencyToken(propertyValue.Consistency) + existingPropertyValueID := rows[0].PropertyValue.ID + var snapshot *models.PropertyValue + a, snapshot, err = aggregate.LoadPropertyValueAggregateWithSnapshotAt(ctx, as, existingPropertyValueID, expConsistency) + if err != nil { + return 0, nil, err + } // conflict detection + consistency := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && consistency != *expConsistency { - conflict, err := propertyValueRowsToMessage(rows) - return consistency, conflict, err - } - - if a, err = aggregate.LoadPropertyValueAggregate(ctx, as, existingPropertyValueID); err != nil { - return 0, nil, err + return consistency, &AttachPropertyValueConflict{ + Was: snapshot, + Is: a.PropertyValue, + }, err } // TODO: update value will be triggered, even if the value is not the type the property defines From 029e529a0cd954fca41f044f74c3eb34570fb5ff Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 11 Oct 2024 15:40:38 +0200 Subject: [PATCH 23/42] qs --- .../property-svc/internal/property-value/api/grpc.go | 10 +++++++--- .../property-svc/stories/PropertyValueCRUD_test.go | 8 +++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index fd407d518..ba83137fd 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -136,10 +136,14 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req conflicts := make(map[string]*commonpb.AttributeConflict) // TODO: find a generic approach - valueUpdateRequested := req.Value != conflict.Is.Value // TODO - valueAlreadyUpdated := conflict.Was.Value != conflict.Is.Value // same TODO + valueUpdateRequested := value != conflict.Is.Value + valueAlreadyUpdated := conflict.Was.Value != conflict.Is.Value if valueUpdateRequested && valueAlreadyUpdated { - var is proto.Message = nil // TODO + var is proto.Message + isValue := conflict.Is.Value + + // TODO + conflicts["value"], err = util.AttributeConflict( is, want, diff --git a/services/property-svc/stories/PropertyValueCRUD_test.go b/services/property-svc/stories/PropertyValueCRUD_test.go index f1a8e9b41..69cff7c83 100644 --- a/services/property-svc/stories/PropertyValueCRUD_test.go +++ b/services/property-svc/stories/PropertyValueCRUD_test.go @@ -100,6 +100,8 @@ func TestCreateAttachUpdateTextProperty(t *testing.T) { return } + assert.Nil(t, attachResponse.Conflict) + hwtesting.WaitForProjectionsToSettle() // @@ -140,6 +142,7 @@ func TestCreateAttachUpdateTextProperty(t *testing.T) { return } + assert.Nil(t, updateResponse.Conflict) assert.NotEqual(t, attachedValuesResponse.Values[0].ValueConsistency, &updateResponse.Consistency) hwtesting.WaitForProjectionsToSettle() @@ -274,6 +277,7 @@ func TestCreateAttachUpdateSelectProperty(t *testing.T) { if !assert.NoError(t, err, "could not attach value") { return } + assert.Nil(t, attachResponse.Conflict) hwtesting.WaitForProjectionsToSettle() @@ -314,7 +318,7 @@ func TestCreateAttachUpdateSelectProperty(t *testing.T) { if !assert.NoError(t, err, "could not update value") { return } - + assert.Nil(t, updateResponse.Conflict) assert.NotEqual(t, attachedValuesResponse.Values[0].ValueConsistency, &updateResponse.Consistency) hwtesting.WaitForProjectionsToSettle() @@ -457,6 +461,7 @@ func TestCreateAttachUpdateMultiSelectProperty(t *testing.T) { if !assert.NoError(t, err, "could not attach value") { return } + assert.Nil(t, attachResponse.Conflict) hwtesting.WaitForProjectionsToSettle() @@ -500,6 +505,7 @@ func TestCreateAttachUpdateMultiSelectProperty(t *testing.T) { if !assert.NoError(t, err, "could not update value") { return } + assert.Nil(t, updateResponse.Conflict) assert.NotEqual(t, attachedValuesResponse.Values[0].ValueConsistency, &updateResponse.Consistency) From a13ad2ff736ccfcb489dde0459f80ea7e88f2b68 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 11 Oct 2024 18:45:57 +0200 Subject: [PATCH 24/42] refactorino --- gen/go/services/property_svc/v1/export.go | 6 + libs/hwes/event.go | 56 ++-- libs/hwtesting/grpc.go | 30 +- libs/hwutil/arrays.go | 17 + proto/services/property_svc/v1/types.proto | 2 +- .../property-value/aggregate/actions.go | 9 +- .../property-value/aggregate/aggregate.go | 26 +- .../internal/property-value/api/grpc.go | 86 ++++-- .../commands/v1/attach_property_value.go | 26 +- .../property-value/events/v1/events.go | 25 +- .../property-value/models/propertyValue.go | 126 +++++--- .../property_value_postgres_projection.go | 291 ++++++------------ .../property_postgres_projection.go | 16 +- .../v1/get_properties_by_subject_type.go | 9 +- .../000009_properties_organization.down.sql | 1 + .../000009_properties_organization.up.sql | 2 + .../repos/patient_views_repo/models.go | 19 +- services/property-svc/repos/property_repo.sql | 8 +- .../repos/property_repo/models.go | 19 +- .../repos/property_repo/property_repo.sql.go | 31 +- .../repos/property_value_repo.sql | 16 +- .../repos/property_value_repo/interfaces.go | 52 ++++ .../repos/property_value_repo/models.go | 19 +- .../property_value_repo.sql.go | 65 +--- .../repos/task_views_repo/models.go | 19 +- .../property-svc/repos/views_repo/models.go | 19 +- services/property-svc/schema.sql | 3 +- .../stories/GetPropertieBySubjectType_test.go | 3 +- .../stories/PropertyValueCRUD_test.go | 32 +- services/property-svc/stories/setup_test.go | 6 +- .../tasks-svc/stories/PatientCRUD_test.go | 2 +- services/tasks-svc/stories/TaskCRUD_test.go | 2 +- services/tasks-svc/stories/WardCRUD_test.go | 6 +- services/tasks-svc/stories/setup_test.go | 12 +- 34 files changed, 563 insertions(+), 498 deletions(-) create mode 100644 gen/go/services/property_svc/v1/export.go create mode 100644 services/property-svc/migrations/000009_properties_organization.down.sql create mode 100644 services/property-svc/migrations/000009_properties_organization.up.sql create mode 100644 services/property-svc/repos/property_value_repo/interfaces.go diff --git a/gen/go/services/property_svc/v1/export.go b/gen/go/services/property_svc/v1/export.go new file mode 100644 index 000000000..0f18f626f --- /dev/null +++ b/gen/go/services/property_svc/v1/export.go @@ -0,0 +1,6 @@ +package v1 + +// This file is handwritten and exists because the go codegen is horseshit. +// It exports hidden types needed to properly work with oneofs. + +type IsAttachPropertyValueRequest_Value = isAttachPropertyValueRequest_Value diff --git a/libs/hwes/event.go b/libs/hwes/event.go index ab4e0b905..75f58c43b 100644 --- a/libs/hwes/event.go +++ b/libs/hwes/event.go @@ -32,6 +32,8 @@ type Event struct { Version uint64 // user responsible for this event CommitterUserID *uuid.UUID + // org of user responsible for this event + CommitterOrganizationID *uuid.UUID // w3c trace context TraceParent string } @@ -39,6 +41,8 @@ type Event struct { type metadata struct { // CommitterUserID represents an optional UUID that identifies the user that is directly responsible for this event CommitterUserID string `json:"committer_user_id"` + // CommitterOrganizationID represents an optional UUID that identifies the user that is directly responsible for this event + CommitterOrganizationID string `json:"committer_org"` // w3c trace context TraceParent string `json:"trace_parent"` // The Timestamp represents the time when the event was created. Using the built-in eventstoreDB timestamp is discouraged. @@ -65,12 +69,13 @@ func WithData(data interface{}) EventOption { func NewEvent(aggregate Aggregate, eventType string, opts ...EventOption) (Event, error) { evt := Event{ - EventID: uuid.New(), - EventType: eventType, - AggregateID: aggregate.GetID(), - AggregateType: aggregate.GetType(), - Timestamp: time.Now().UTC(), - CommitterUserID: nil, + EventID: uuid.New(), + EventType: eventType, + AggregateID: aggregate.GetID(), + AggregateType: aggregate.GetType(), + Timestamp: time.Now().UTC(), + CommitterUserID: nil, + CommitterOrganizationID: nil, } // TODO: We have to default to empty eventData as the eventstoredb-ui does not allow querying events without data @@ -140,21 +145,26 @@ func NewEventFromRecordedEvent(esdbEvent *esdb.RecordedEvent) (Event, error) { } event := Event{ - EventID: id, - EventType: esdbEvent.EventType, - AggregateID: aggregateID, - AggregateType: aggregateType, - Data: esdbEvent.Data, - Timestamp: md.Timestamp, - Version: esdbEvent.EventNumber, - CommitterUserID: nil, - TraceParent: md.TraceParent, + EventID: id, + EventType: esdbEvent.EventType, + AggregateID: aggregateID, + AggregateType: aggregateType, + Data: esdbEvent.Data, + Timestamp: md.Timestamp, + Version: esdbEvent.EventNumber, + CommitterUserID: nil, + CommitterOrganizationID: nil, + TraceParent: md.TraceParent, } eventCommitterUserID, err := uuid.Parse(md.CommitterUserID) if err == nil { event.CommitterUserID = &eventCommitterUserID } + eventCommitterOrgID, err := uuid.Parse(md.CommitterOrganizationID) + if err == nil { + event.CommitterOrganizationID = &eventCommitterOrgID + } return event, nil } @@ -188,6 +198,7 @@ func (e *Event) ToEventData() (esdb.EventData, error) { } if e.CommitterUserID != nil { md.CommitterUserID = e.CommitterUserID.String() + md.CommitterOrganizationID = e.CommitterOrganizationID.String() } mdBytes, err := json.Marshal(md) @@ -245,14 +256,16 @@ func (e *Event) SetCommitterFromCtx(ctx context.Context) error { return nil // don't set a user, if no user is available } - e.CommitterUserID = &userID - - // Just to make sure we are actually dealing with a valid UUID - if _, err := uuid.Parse(e.CommitterUserID.String()); err != nil { - return fmt.Errorf("SetCommitterFromCtx: cant parse comitter uid: %w", err) + orgID, err := common.GetOrganizationID(ctx) + if err != nil { + return nil // don't set a user, if no user is available } + e.CommitterUserID = &userID + e.CommitterOrganizationID = &orgID + telemetry.SetSpanStr(ctx, "committerUserID", e.CommitterUserID.String()) + telemetry.SetSpanStr(ctx, "committerOrganizationID", e.CommitterOrganizationID.String()) return nil } @@ -279,6 +292,9 @@ func (e *Event) GetZerologDict() *zerolog.Event { if e.CommitterUserID != nil { dict.Str("committerUserID", e.CommitterUserID.String()) } + if e.CommitterOrganizationID != nil { + dict.Str("committerOrganizaionID", e.CommitterOrganizationID.String()) + } return dict } diff --git a/libs/hwtesting/grpc.go b/libs/hwtesting/grpc.go index f67c846cb..0d8464ef9 100644 --- a/libs/hwtesting/grpc.go +++ b/libs/hwtesting/grpc.go @@ -21,12 +21,19 @@ import ( // ) // // Also see GetFakeTokenCredentials -type InsecureBearerToken string +type InsecureBearerToken struct { + bearer string + orgMD string +} func (t InsecureBearerToken) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { - return map[string]string{ - "authorization": "Bearer " + string(t), - }, nil + m := map[string]string{ + "authorization": "Bearer " + t.bearer, + } + if t.orgMD != "" { + m["X-Organization"] = t.orgMD + } + return m, nil } func (t InsecureBearerToken) RequireTransportSecurity() bool { return false @@ -34,7 +41,7 @@ func (t InsecureBearerToken) RequireTransportSecurity() bool { const FakeTokenUser = "18159713-5d4e-4ad5-94ad-fbb6bb147984" -func GetFakeTokenCredentials(subOverride string) InsecureBearerToken { +func GetFakeTokenCredentials(subOverride, orgOverride string) InsecureBearerToken { // README's fake token m := map[string]interface{}{ "sub": FakeTokenUser, @@ -50,19 +57,26 @@ func GetFakeTokenCredentials(subOverride string) InsecureBearerToken { m["sub"] = subOverride } + if orgOverride != "" { + m["organizations"] = []string{orgOverride} + } + bytes, err := json.Marshal(m) if err != nil { panic(fmt.Errorf("GetFakeTokenCredentials failed: %w", err)) } dist := make([]byte, base64.StdEncoding.EncodedLen(len(bytes))) base64.StdEncoding.Encode(dist, bytes) - return InsecureBearerToken(dist) + return InsecureBearerToken{ + bearer: string(dist), + orgMD: orgOverride, + } } -func GetGrpcConn(subOverride string) *grpc.ClientConn { +func GetGrpcConn(subOverride, orgOverride string) *grpc.ClientConn { conn, err := grpc.NewClient( common.ResolveAddrFromEnv(), - grpc.WithPerRPCCredentials(GetFakeTokenCredentials(subOverride)), + grpc.WithPerRPCCredentials(GetFakeTokenCredentials(subOverride, orgOverride)), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { diff --git a/libs/hwutil/arrays.go b/libs/hwutil/arrays.go index 8fbdfbe49..53a27b512 100644 --- a/libs/hwutil/arrays.go +++ b/libs/hwutil/arrays.go @@ -169,3 +169,20 @@ func OrEmptySlice[T any](as []T) []T { } return make([]T, 0) } + +func Without[T comparable](original, itemsToRemove []T) []T { + // Create a map for quick lookup of items to remove + toRemove := make(map[T]bool) + for _, item := range itemsToRemove { + toRemove[item] = true + } + + var result []T + for _, item := range original { + if _, exists := toRemove[item]; !exists { + result = append(result, item) + } + } + + return result +} diff --git a/proto/services/property_svc/v1/types.proto b/proto/services/property_svc/v1/types.proto index 94295c30b..e1e9b35db 100644 --- a/proto/services/property_svc/v1/types.proto +++ b/proto/services/property_svc/v1/types.proto @@ -8,7 +8,7 @@ import "google/protobuf/timestamp.proto"; message Date { // information more precise than date information shall be disregarded by clients - google.protobuf.Timestamp date = 1; + google.protobuf.Timestamp date = 1; // @gotags: validate:"required" } enum SubjectType { diff --git a/services/property-svc/internal/property-value/aggregate/actions.go b/services/property-svc/internal/property-value/aggregate/actions.go index 1c20ad4dc..a67b73355 100644 --- a/services/property-svc/internal/property-value/aggregate/actions.go +++ b/services/property-svc/internal/property-value/aggregate/actions.go @@ -5,20 +5,21 @@ import ( "fmt" "github.com/google/uuid" propertyEventsV1 "property-svc/internal/property-value/events/v1" + "property-svc/internal/property-value/models" ) -func (a *PropertyValueAggregate) CreatePropertyValue(ctx context.Context, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID) error { +func (a *PropertyValueAggregate) CreatePropertyValue(ctx context.Context, propertyID uuid.UUID, valueChange models.TypedValueChange, subjectID uuid.UUID) error { id := a.GetID() - event, err := propertyEventsV1.NewPropertyValueCreatedEvent(ctx, a, id, propertyID, value, subjectID) + event, err := propertyEventsV1.NewPropertyValueCreatedEvent(ctx, a, id, propertyID, valueChange, subjectID) if err != nil { return fmt.Errorf("PropertyValueAggregate.CreatePropertyValue: %w", err) } return a.Apply(event) } -func (a *PropertyValueAggregate) UpdatePropertyValue(ctx context.Context, value interface{}) error { - event, err := propertyEventsV1.NewPropertyValueUpdatedEvent(ctx, a, value) +func (a *PropertyValueAggregate) UpdatePropertyValue(ctx context.Context, valueChange models.TypedValueChange) error { + event, err := propertyEventsV1.NewPropertyValueUpdatedEvent(ctx, a, valueChange) if err != nil { return fmt.Errorf("PropertyValueAggregate.UpdatePropertyValue: %w", err) } diff --git a/services/property-svc/internal/property-value/aggregate/aggregate.go b/services/property-svc/internal/property-value/aggregate/aggregate.go index 74113b3ca..dda0967f2 100644 --- a/services/property-svc/internal/property-value/aggregate/aggregate.go +++ b/services/property-svc/internal/property-value/aggregate/aggregate.go @@ -18,9 +18,15 @@ type PropertyValueAggregate struct { } func NewPropertyValueAggregate(id uuid.UUID) *PropertyValueAggregate { - aggregate := &PropertyValueAggregate{PropertyValue: models.NewPropertyValue()} - aggregate.AggregateBase = hwes.NewAggregateBase(PropertyValueAggregateType, id) - aggregate.PropertyValue.ID = id + aggregate := &PropertyValueAggregate{ + PropertyValue: &models.PropertyValue{ + ID: id, + PropertyID: uuid.UUID{}, + SubjectID: uuid.UUID{}, + Value: nil, + }, + AggregateBase: hwes.NewAggregateBase(PropertyValueAggregateType, id), + } aggregate.initEventListeners() return aggregate } @@ -45,7 +51,7 @@ func LoadPropertyValueAggregateWithSnapshotAt(ctx context.Context, as hwes.Aggre } cpy := *property.PropertyValue // deref does a top-level copy - // well see how it deals with .Value, of type interface (TODO) + // well see how it deals with .ValueChange, of type interface (TODO) snapshot = &cpy } @@ -79,9 +85,12 @@ func (a *PropertyValueAggregate) onPropertyValueCreated(evt hwes.Event) error { } a.PropertyValue.PropertyID = propertyID - a.PropertyValue.Value = payload.Value a.PropertyValue.SubjectID = subjectID + value := &models.SimpleTypedValue{} + payload.ValueChange.Apply(value) + a.PropertyValue.Value = value + return nil } @@ -91,7 +100,12 @@ func (a *PropertyValueAggregate) onPropertyValueUpdated(evt hwes.Event) error { return err } - a.PropertyValue.Value = payload.Value + if payload.ValueChange.ValueRemoved { + a.PropertyValue.Value = nil + return nil + } + + payload.ValueChange.Apply(a.PropertyValue.Value) return nil } diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index ba83137fd..79ceba9f7 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -8,9 +8,10 @@ import ( pb "gen/services/property_svc/v1" "github.com/google/uuid" zlog "github.com/rs/zerolog/log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" "hwes" "hwutil" "property-svc/internal/property-value/handlers" @@ -72,43 +73,61 @@ func NewPropertyValueService(aggregateStore hwes.AggregateStore, handlers *handl return &PropertyValueGrpcService{as: aggregateStore, handlers: handlers} } -func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req *pb.AttachPropertyValueRequest) (*pb.AttachPropertyValueResponse, error) { - log := zlog.Ctx(ctx) - propertyValueID := uuid.New() - - propertyID := uuid.MustParse(req.GetPropertyId()) // guarded by validate - subjectID := uuid.MustParse(req.GetSubjectId()) // guarded by validate - - var want proto.Message - var value interface{} - switch req.Value.(type) { +func toTypedValueChange(value pb.IsAttachPropertyValueRequest_Value) (typedValue *models.TypedValueChange) { + if value == nil { + return &models.TypedValueChange{ + ValueRemoved: true, + } + } + switch value.(type) { case *pb.AttachPropertyValueRequest_TextValue: - value = req.GetTextValue() - want = wrapperspb.String(req.GetTextValue()) + return &models.TypedValueChange{ + TextValue: &value.(*pb.AttachPropertyValueRequest_TextValue).TextValue, + } case *pb.AttachPropertyValueRequest_NumberValue: - value = req.GetNumberValue() - want = wrapperspb.Double(req.GetNumberValue()) + return &models.TypedValueChange{ + NumberValue: &value.(*pb.AttachPropertyValueRequest_NumberValue).NumberValue, + } case *pb.AttachPropertyValueRequest_BoolValue: - value = req.GetBoolValue() - want = wrapperspb.Bool(req.GetBoolValue()) + return &models.TypedValueChange{ + BoolValue: &value.(*pb.AttachPropertyValueRequest_BoolValue).BoolValue, + } case *pb.AttachPropertyValueRequest_DateValue: - value = req.GetDateValue().Date - want = &pb.Date{Date: req.GetDateTimeValue()} + return &models.TypedValueChange{ + DateValue: hwutil.PtrTo(value.(*pb.AttachPropertyValueRequest_DateValue).DateValue.Date.AsTime()), + } case *pb.AttachPropertyValueRequest_DateTimeValue: - value = req.GetDateTimeValue() - want = req.GetDateTimeValue() + return &models.TypedValueChange{ + DateTimeValue: hwutil.PtrTo(value.(*pb.AttachPropertyValueRequest_DateTimeValue).DateTimeValue.AsTime()), + } case *pb.AttachPropertyValueRequest_SelectValue: - value = req.GetSelectValue() - want = wrapperspb.String(req.GetSelectValue()) // IS will be a pb.SelectValueOption, not a str, might be confusing for the frontend, but I'm not going to query the db for this + return &models.TypedValueChange{ + SingleSelectValue: &value.(*pb.AttachPropertyValueRequest_SelectValue).SelectValue, + } case *pb.AttachPropertyValueRequest_MultiSelectValue_: - msv := req.GetMultiSelectValue() - value = models.MultiSelectChange{ - SelectValues: msv.SelectValues, - RemoveSelectValues: msv.RemoveSelectValues, + msv := value.(*pb.AttachPropertyValueRequest_MultiSelectValue_).MultiSelectValue + return &models.TypedValueChange{ + MultiSelectValues: &models.MultiSelectChange{ + SelectValues: msv.SelectValues, + RemoveSelectValues: msv.RemoveSelectValues, + }, } - want = req.GetMultiSelectValue() default: - value = nil + return nil + } +} + +func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req *pb.AttachPropertyValueRequest) (*pb.AttachPropertyValueResponse, error) { + log := zlog.Ctx(ctx) + propertyValueID := uuid.New() + + propertyID := uuid.MustParse(req.GetPropertyId()) // guarded by validate + subjectID := uuid.MustParse(req.GetSubjectId()) // guarded by validate + + valueChange := toTypedValueChange(req.Value) + if valueChange == nil { + log.Error().Type("valueType", req.Value).Msg("req.ValueChange type is not known") + return nil, status.Error(codes.Internal, "failed to parse value") } expConsistency, ok := common.ParseConsistency(req.Consistency) @@ -124,7 +143,7 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req return nil, fmt.Errorf("failed conflict resolution") } - c, conflict, err := s.handlers.Commands.V1.AttachPropertyValue(ctx, propertyValueID, propertyID, value, subjectID, expConsistency) + c, conflict, err := s.handlers.Commands.V1.AttachPropertyValue(ctx, propertyValueID, propertyID, *valueChange, subjectID, expConsistency) if err != nil { return nil, err } @@ -136,11 +155,12 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req conflicts := make(map[string]*commonpb.AttributeConflict) // TODO: find a generic approach - valueUpdateRequested := value != conflict.Is.Value + valueUpdateRequested := false // TODO valueChange != conflict.Is.Value valueAlreadyUpdated := conflict.Was.Value != conflict.Is.Value if valueUpdateRequested && valueAlreadyUpdated { var is proto.Message - isValue := conflict.Is.Value + var want proto.Message + // TODO: isValue := conflict.Is.Value // TODO @@ -241,7 +261,7 @@ func (s *PropertyValueGrpcService) GetAttachedPropertyValues(ctx context.Context }, } default: - log.Error().Any("pnv", pnv).Msg("pnv.Value is in an invalid state!") + log.Error().Any("pnv", pnv).Msg("pnv.ValueChange is in an invalid state!") } return res }), diff --git a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go index 605fbbd09..94a428984 100644 --- a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go +++ b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go @@ -14,7 +14,7 @@ import ( type AttachPropertyValueCommandHandler func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, - value interface{}, + valueChange models.TypedValueChange, subjectID uuid.UUID, expConsistency *common.ConsistencyToken, ) (common.ConsistencyToken, *AttachPropertyValueConflict, error) @@ -25,11 +25,13 @@ type AttachPropertyValueConflict struct { } func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachPropertyValueCommandHandler { - return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AttachPropertyValueConflict, error) { + return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, valueChange models.TypedValueChange, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AttachPropertyValueConflict, error) { propertyValueRepo := property_value_repo.New(hwdb.GetDB()) var a *aggregate.PropertyValueAggregate - rows, err := propertyValueRepo.GetPropertyValueBySubjectIDAndPropertyID(ctx, property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDParams{ + // first check if a value is already attached to the subject for this property + query := hwdb.Optional(propertyValueRepo.GetPropertyValueBySubjectIDAndPropertyID) + existingPropertyValueID, err := query(ctx, property_value_repo.GetPropertyValueBySubjectIDAndPropertyIDParams{ PropertyID: propertyID, SubjectID: subjectID, }) @@ -37,10 +39,15 @@ func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachProperty return 0, nil, err } - if len(rows) != 0 { - existingPropertyValueID := rows[0].PropertyValue.ID + // if no value exists yet, create one + if existingPropertyValueID == nil { + a = aggregate.NewPropertyValueAggregate(propertyValueID) + if err := a.CreatePropertyValue(ctx, propertyID, valueChange, subjectID); err != nil { + return 0, nil, err + } + } else { // else, update the existing value var snapshot *models.PropertyValue - a, snapshot, err = aggregate.LoadPropertyValueAggregateWithSnapshotAt(ctx, as, existingPropertyValueID, expConsistency) + a, snapshot, err = aggregate.LoadPropertyValueAggregateWithSnapshotAt(ctx, as, *existingPropertyValueID, expConsistency) if err != nil { return 0, nil, err } @@ -56,12 +63,7 @@ func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachProperty // TODO: update value will be triggered, even if the value is not the type the property defines - if err := a.UpdatePropertyValue(ctx, value); err != nil { - return 0, nil, err - } - } else { - a = aggregate.NewPropertyValueAggregate(propertyValueID) - if err := a.CreatePropertyValue(ctx, propertyID, value, subjectID); err != nil { + if err := a.UpdatePropertyValue(ctx, valueChange); err != nil { return 0, nil, err } } diff --git a/services/property-svc/internal/property-value/events/v1/events.go b/services/property-svc/internal/property-value/events/v1/events.go index 789c31863..aff37651b 100644 --- a/services/property-svc/internal/property-value/events/v1/events.go +++ b/services/property-svc/internal/property-value/events/v1/events.go @@ -4,6 +4,7 @@ import ( "context" "github.com/google/uuid" "hwes" + "property-svc/internal/property-value/models" ) const ( @@ -12,29 +13,29 @@ const ( ) type PropertyValueCreatedEvent struct { - ID string `json:"id"` - PropertyID string `json:"property_id"` - Value interface{} `json:"value"` - SubjectID string `json:"subject_id"` + ID string `json:"id"` + PropertyID string `json:"property_id"` + ValueChange models.TypedValueChange `json:"value_change"` + SubjectID string `json:"subject_id"` } -func NewPropertyValueCreatedEvent(ctx context.Context, a hwes.Aggregate, id uuid.UUID, propertyID uuid.UUID, value interface{}, subjectID uuid.UUID) (hwes.Event, error) { +func NewPropertyValueCreatedEvent(ctx context.Context, a hwes.Aggregate, id uuid.UUID, propertyID uuid.UUID, valueChange models.TypedValueChange, subjectID uuid.UUID) (hwes.Event, error) { payload := PropertyValueCreatedEvent{ - ID: id.String(), - PropertyID: propertyID.String(), - Value: value, - SubjectID: subjectID.String(), + ID: id.String(), + PropertyID: propertyID.String(), + ValueChange: valueChange, + SubjectID: subjectID.String(), } return hwes.NewEvent(a, PropertyValueCreated, hwes.WithData(payload), hwes.WithContext(ctx)) } type PropertyValueUpdatedEvent struct { - Value interface{} `json:"value"` + ValueChange models.TypedValueChange `json:"value_change"` } -func NewPropertyValueUpdatedEvent(ctx context.Context, a hwes.Aggregate, value interface{}) (hwes.Event, error) { +func NewPropertyValueUpdatedEvent(ctx context.Context, a hwes.Aggregate, value models.TypedValueChange) (hwes.Event, error) { payload := PropertyValueUpdatedEvent{ - Value: value, + ValueChange: value, } return hwes.NewEvent(a, PropertyValueUpdated, hwes.WithData(payload), hwes.WithContext(ctx)) } diff --git a/services/property-svc/internal/property-value/models/propertyValue.go b/services/property-svc/internal/property-value/models/propertyValue.go index dd3fd089d..828fe6f1b 100644 --- a/services/property-svc/internal/property-value/models/propertyValue.go +++ b/services/property-svc/internal/property-value/models/propertyValue.go @@ -1,9 +1,10 @@ package models import ( - "fmt" pb "gen/services/property_svc/v1" "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "hwdb" "hwutil" "time" ) @@ -12,11 +13,7 @@ type PropertyValue struct { ID uuid.UUID PropertyID uuid.UUID SubjectID uuid.UUID - Value interface{} -} - -func NewPropertyValue() *PropertyValue { - return &PropertyValue{} + Value *SimpleTypedValue } type PropertyValueWithProperty struct { @@ -34,18 +31,35 @@ type PropertyValueWithProperty struct { } type SelectValueOption struct { - Id uuid.UUID - Name string - Description string + Id uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +// TODO +type SimpleTypedValue struct { + TextValue *string + BoolValue *bool + NumberValue *float64 + DateTimeValue *time.Time + DateValue *time.Time + SingleSelectValue *string + MultiSelectValues []string } type TypedValue struct { TextValue *string BoolValue *bool NumberValue *float64 - MultiSelectValues []SelectValueOption DateTimeValue *time.Time DateValue *time.Time + SingleSelectValue *SelectValueOption + MultiSelectValues []SelectValueOption +} + +type MultiSelectChange struct { + SelectValues []string `json:"select_values,omitempty"` + RemoveSelectValues []string `json:"remove_select_values,omitempty"` } type PropertyAndValue struct { @@ -65,54 +79,66 @@ type PropertyAndValue struct { Value *TypedValue } -type MultiSelectChange struct { - SelectValues []string - RemoveSelectValues []string +type TypedValueChange struct { + ValueRemoved bool `json:"value_removed,omitempty"` + TextValue *string `json:"text_value,omitempty"` + BoolValue *bool `json:"bool_value,omitempty"` + NumberValue *float64 `json:"number_value,omitempty"` + DateTimeValue *time.Time `json:"date_time_value,omitempty"` + DateValue *time.Time `json:"date_value,omitempty"` + SingleSelectValue *string `json:"single_select_value,omitempty"` + MultiSelectValues *MultiSelectChange `json:"multi_select_values,omitempty"` } -func interfaceToStringSlice(interf interface{}) ([]string, error) { - slice, ok := interf.([]interface{}) - if !ok { - return nil, fmt.Errorf("value is not a slice") +// Apply applies a TypedValueChange to a TypedValue (except removal) +func (c TypedValueChange) Apply(value *SimpleTypedValue) bool { + switch { + case c.TextValue != nil: + value.TextValue = c.TextValue + case c.NumberValue != nil: + value.NumberValue = c.NumberValue + case c.BoolValue != nil: + value.BoolValue = c.BoolValue + case c.DateValue != nil: + value.DateValue = c.DateValue + case c.DateTimeValue != nil: + value.DateTimeValue = c.DateTimeValue + case c.SingleSelectValue != nil: + value.SingleSelectValue = c.SingleSelectValue + case c.MultiSelectValues != nil: + value.MultiSelectValues = hwutil.Without(value.MultiSelectValues, c.MultiSelectValues.RemoveSelectValues) + value.MultiSelectValues = append(value.MultiSelectValues, c.MultiSelectValues.SelectValues...) + + default: + return false } - strings, ok := hwutil.InterfacesToStrings(slice) - if !ok { - return nil, fmt.Errorf("value is not a []string") - } - return strings, nil + return true } -func MultiSelectChangeFromMap(m map[string]interface{}) (MultiSelectChange, error) { - self := MultiSelectChange{} - if m["SelectValues"] == nil { - self.SelectValues = []string{} - } else { - selectValues, err := interfaceToStringSlice(m["SelectValues"]) - if err != nil { - return MultiSelectChange{}, fmt.Errorf("MultiSelectChangeFromMap: could not parse \"SelectValues\": %w", err) - } - self.SelectValues = selectValues - } - - if m["RemoveSelectValues"] == nil { - self.RemoveSelectValues = []string{} - } else { - removeSelectValues, err := interfaceToStringSlice(m["RemoveSelectValues"]) - if err != nil { - return MultiSelectChange{}, fmt.Errorf("MultiSelectChangeFromMap: could not parse \"RemoveSelectValues\": %w", err) - } - self.RemoveSelectValues = removeSelectValues - } - - return self, nil +type BasicChangeSettable interface { + SetTextValue(*string) + SetNumberValue(*float64) + SetBoolValue(*bool) + SetDateValue(pgtype.Date) + SetDateTimeValue(pgtype.Timestamp) } -func MultiSelectChangeFromInterface(value interface{}) (MultiSelectChange, error) { - m, ok := value.(map[string]interface{}) - if !ok { - return MultiSelectChange{}, fmt.Errorf("MultiSelectChangeFromInterface: value is not a map") +func (c TypedValueChange) SetBasicValues(settable BasicChangeSettable) bool { + switch { + case c.TextValue != nil: + settable.SetTextValue(c.TextValue) + case c.NumberValue != nil: + settable.SetNumberValue(c.NumberValue) + case c.BoolValue != nil: + settable.SetBoolValue(c.BoolValue) + case c.DateValue != nil: + settable.SetDateValue(hwdb.TimeToDate(*c.DateValue)) + case c.DateTimeValue != nil: + settable.SetDateTimeValue(hwdb.TimeToTimestamp(*c.DateTimeValue)) + default: + return false } - return MultiSelectChangeFromMap(m) + return true } diff --git a/services/property-svc/internal/property-value/projections/property_value_postgres_projection/property_value_postgres_projection.go b/services/property-svc/internal/property-value/projections/property_value_postgres_projection/property_value_postgres_projection.go index 052f183d6..99c219cb8 100644 --- a/services/property-svc/internal/property-value/projections/property_value_postgres_projection/property_value_postgres_projection.go +++ b/services/property-svc/internal/property-value/projections/property_value_postgres_projection/property_value_postgres_projection.go @@ -3,7 +3,6 @@ package property_value_postgres_projection import ( "context" "fmt" - pb "gen/services/property_svc/v1" "github.com/EventStore/EventStore-Client-Go/v4/esdb" "github.com/google/uuid" zlog "github.com/rs/zerolog/log" @@ -50,9 +49,7 @@ func (p *Projection) onPropertyValueCreated(ctx context.Context, evt hwes.Event) return err, hwutil.PtrTo(esdb.NackActionPark) } - if payload.Value == nil { - return fmt.Errorf("onPropertyValueCreated: payload is empty"), hwutil.PtrTo(esdb.NackActionPark) - } + valueChange := payload.ValueChange propertyID, err := uuid.Parse(payload.PropertyID) if err != nil { @@ -64,16 +61,7 @@ func (p *Projection) onPropertyValueCreated(ctx context.Context, evt hwes.Event) return err, hwutil.PtrTo(esdb.NackActionPark) } - // GetProperty for the fieldType - property, err := hwdb.Optional(p.propertyRepo.GetPropertyById)(ctx, propertyID) - if property == nil { - return fmt.Errorf("property with id %s not found for propertyValue", payload.PropertyID), hwutil.PtrTo(esdb.NackActionRetry) - } - if err := hwdb.Error(ctx, err); err != nil { - return err, hwutil.PtrTo(esdb.NackActionRetry) - } - fieldType := (pb.FieldType)(property.FieldType) - + // Start TX tx, rollback, err := hwdb.BeginTx(p.db, ctx) if err != nil { return fmt.Errorf("could not start tx: %w", err), hwutil.PtrTo(esdb.NackActionRetry) @@ -82,35 +70,27 @@ func (p *Projection) onPropertyValueCreated(ctx context.Context, evt hwes.Event) repo := p.propertyValueRepo.WithTx(tx) - err, ack := createBasicPropertyValue(ctx, evt, repo, payload, propertyID, evt.AggregateID, subjectID, fieldType) + // first create a new value, this will also immediately store the value for basic types (e.g., text, number, bool, ...) + err, ack := createBasicPropertyValue(ctx, evt, repo, valueChange, propertyID, evt.AggregateID, subjectID) if err != nil { return fmt.Errorf("onPropertyValueCreated: could not createbasicPropertyValue: %w", err), ack } - if fieldType == pb.FieldType_FIELD_TYPE_SELECT || fieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT { + // for selects we are going to add options into the appropriate tables as well + isSingleSelectChange := valueChange.SingleSelectValue != nil + isMultiSelectChange := valueChange.MultiSelectValues != nil + + if isSingleSelectChange || isMultiSelectChange { var selectsToAdd []property_value_repo.ConnectValueWithSelectOptionsParams - if fieldType == pb.FieldType_FIELD_TYPE_SELECT { - val, ok := payload.Value.(string) - if !ok { - return fmt.Errorf("could not assert string"), hwutil.PtrTo(esdb.NackActionPark) - } - id, err := uuid.Parse(val) + if isSingleSelectChange { + id, err := uuid.Parse(*valueChange.SingleSelectValue) if err != nil { - return err, hwutil.PtrTo(esdb.NackActionPark) + return fmt.Errorf("option change is not a uuid: %w", err), hwutil.PtrTo(esdb.NackActionSkip) } selectsToAdd = []property_value_repo.ConnectValueWithSelectOptionsParams{{ValueID: evt.AggregateID, SelectOption: id}} - } else { - // FieldType_FIELD_TYPE_MULTI_SELECT - multiSelectChange, err := models.MultiSelectChangeFromInterface(payload.Value) - if err != nil { - log.Error(). - Err(err). - Type("value_type", payload.Value). - Any("value", payload.Value). - Msg("onPropertyValueCreated: could not parse payload.Value as MultiSelectChange") - return fmt.Errorf("onPropertyValueCreated: could not parse payload.Value as MultiSelectChange: %w", err), hwutil.PtrTo(esdb.NackActionPark) - } + } else { // isMultiSelectChange + multiSelectChange := payload.ValueChange.MultiSelectValues ids, err := hwutil.StringsToUUIDs(multiSelectChange.SelectValues) if err != nil { return fmt.Errorf("onPropertyValueCreated: at least one select option uuid could not be parsed: %w", err), hwutil.PtrTo(esdb.NackActionPark) @@ -129,6 +109,7 @@ func (p *Projection) onPropertyValueCreated(ctx context.Context, evt hwes.Event) } } + // Commit if err := tx.Commit(ctx); err != nil { return fmt.Errorf("could not commit transaction: %w", err), hwutil.PtrTo(esdb.NackActionRetry) } @@ -137,7 +118,7 @@ func (p *Projection) onPropertyValueCreated(ctx context.Context, evt hwes.Event) } // createBasicPropertyValue is meant to be called only in onPropertyValueCreated -func createBasicPropertyValue(ctx context.Context, evt hwes.Event, repo *property_value_repo.Queries, payload propertyValueEventsV1.PropertyValueCreatedEvent, propertyID, aggregateID, subjectID uuid.UUID, fieldType pb.FieldType) (error, *esdb.NackAction) { +func createBasicPropertyValue(ctx context.Context, evt hwes.Event, repo *property_value_repo.Queries, valueChange models.TypedValueChange, propertyID, aggregateID, subjectID uuid.UUID) (error, *esdb.NackAction) { createPropertyValueParams := property_value_repo.CreateBasicPropertyValueParams{ ID: aggregateID, PropertyID: propertyID, @@ -145,35 +126,10 @@ func createBasicPropertyValue(ctx context.Context, evt hwes.Event, repo *propert Consistency: int64(evt.GetVersion()), } - switch { - case fieldType == pb.FieldType_FIELD_TYPE_TEXT: - createPropertyValueParams.TextValue = hwutil.PtrTo(fmt.Sprintf("%v", payload.Value)) - case fieldType == pb.FieldType_FIELD_TYPE_NUMBER: - val, ok := payload.Value.(float64) - if !ok { - return fmt.Errorf("could not assert number"), hwutil.PtrTo(esdb.NackActionPark) - } - createPropertyValueParams.NumberValue = &val - case fieldType == pb.FieldType_FIELD_TYPE_CHECKBOX: - val, ok := payload.Value.(bool) - if !ok { - return fmt.Errorf("could not assert bool"), hwutil.PtrTo(esdb.NackActionPark) - } - createPropertyValueParams.BoolValue = &val - case fieldType == pb.FieldType_FIELD_TYPE_DATE: - val, err := hwutil.AssertTimestampToTime(payload.Value) - if err != nil { - return err, hwutil.PtrTo(esdb.NackActionPark) - } - createPropertyValueParams.DateValue = hwdb.TimeToDate(*val) - case fieldType == pb.FieldType_FIELD_TYPE_DATE_TIME: - val, err := hwutil.AssertTimestampToTime(payload.Value) - if err != nil { - return err, hwutil.PtrTo(esdb.NackActionPark) - } - createPropertyValueParams.DateTimeValue = hwdb.TimeToTimestamp(*val) - } + _ = valueChange.SetBasicValues(&createPropertyValueParams) + // do nothing, if nothing was set, we will create the property anyway, we are going to add options to it later in the handler + // create value err := repo.CreateBasicPropertyValue(ctx, createPropertyValueParams) if err := hwdb.Error(ctx, err); err != nil { return err, hwutil.PtrTo(esdb.NackActionRetry) @@ -191,163 +147,120 @@ func (p *Projection) onPropertyValueUpdated(ctx context.Context, evt hwes.Event) return err, hwutil.PtrTo(esdb.NackActionPark) } - // Get Property for FieldType - propertyValue, err := hwdb.Optional(p.propertyValueRepo.GetPropertyValueByID)(ctx, evt.AggregateID) - if propertyValue == nil { - return fmt.Errorf("propertyValue with id %s not found", evt.AggregateID), hwutil.PtrTo(esdb.NackActionRetry) - } - if err := hwdb.Error(ctx, err); err != nil { - return err, hwutil.PtrTo(esdb.NackActionRetry) - } - - property, err := hwdb.Optional(p.propertyRepo.GetPropertyById)(ctx, propertyValue.PropertyID) - if property == nil { - return fmt.Errorf("property with id %s not found for propertyValue", propertyValue.PropertyID.String()), hwutil.PtrTo(esdb.NackActionRetry) - } - if err := hwdb.Error(ctx, err); err != nil { - return err, hwutil.PtrTo(esdb.NackActionRetry) - } + valueChange := payload.ValueChange - fieldType := (pb.FieldType)(property.FieldType) - - if payload.Value == nil { + if valueChange.ValueRemoved { // Delete PropertyValue err := p.propertyValueRepo.DeletePropertyValue(ctx, evt.AggregateID) if err := hwdb.Error(ctx, err); err != nil { return err, hwutil.PtrTo(esdb.NackActionRetry) } - } else if fieldType == pb.FieldType_FIELD_TYPE_SELECT || fieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT { - tx, rollback, err := hwdb.BeginTx(p.db, ctx) - if err != nil { - return fmt.Errorf("could not start tx: %w", err), hwutil.PtrTo(esdb.NackActionRetry) - } - defer rollback() + return nil, nil + } - propertyValueRepo := p.propertyValueRepo.WithTx(tx) + isSingleSelectUpdate := valueChange.SingleSelectValue != nil + isMultiSelectUpdate := valueChange.MultiSelectValues != nil - var toAdd []uuid.UUID - if fieldType == pb.FieldType_FIELD_TYPE_SELECT { - val, ok := payload.Value.(string) - if !ok { - err = fmt.Errorf("onPropertyValueUpdated: could not get select value: type is %T, expected string", payload.Value) - return err, hwutil.PtrTo(esdb.NackActionPark) - } - parsedID, err := uuid.Parse(val) - if err != nil { - return err, hwutil.PtrTo(esdb.NackActionPark) - } + if isSingleSelectUpdate || isMultiSelectUpdate { + return updateSelectPropertyValue(ctx, p.db, evt, valueChange) + } - toAdd = []uuid.UUID{parsedID} + return updateBasicPropertyValue(ctx, evt, p.propertyValueRepo, valueChange) - // delete any other connection - err = propertyValueRepo.DisconnectValueFromAllSelectOptions(ctx, evt.AggregateID) - if err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not disconnectvalue from all options: %w", err), hwutil.PtrTo(esdb.NackActionRetry) - } - } else { - val, err := models.MultiSelectChangeFromInterface(payload.Value) - if err != nil { - log.Error(). - Err(err). - Type("value_type", payload.Value). - Any("value", payload.Value). - Msg("onPropertyValueUpdated: could not parse payload.Value as MultiSelectChange") - return fmt.Errorf("onPropertyValueUpdated: could not parse payload.Value as MultiSelectChange: %w", err), hwutil.PtrTo(esdb.NackActionPark) - } +} - toAdd, err = hwutil.StringsToUUIDs(val.SelectValues) - if err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not parse SelectValues: %w", err), hwutil.PtrTo(esdb.NackActionPark) - } +func updateSelectPropertyValue(ctx context.Context, db hwdb.DBTX, evt hwes.Event, valueChange models.TypedValueChange) (error, *esdb.NackAction) { + aggregateID := evt.AggregateID - // delete requested options - if len(val.RemoveSelectValues) != 0 { - arr, err := hwutil.MapWithErr(val.RemoveSelectValues, func(s string) (property_value_repo.DisconnectValueFromSelectOptionsParams, error) { - id, err := uuid.Parse(s) - if err != nil { - return property_value_repo.DisconnectValueFromSelectOptionsParams{}, err - } - return property_value_repo.DisconnectValueFromSelectOptionsParams{ - ValueID: evt.AggregateID, - SelectOption: id, - }, nil - }) - if err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not parse RemoveSelectValues: %w", err), hwutil.PtrTo(esdb.NackActionPark) - } - if err := hwdb.ExecBatch(propertyValueRepo.DisconnectValueFromSelectOptions(ctx, arr)); err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not disconnect desired options: %w", err), hwutil.PtrTo(esdb.NackActionRetry) - } - } + tx, rollback, err := hwdb.BeginTx(db, ctx) + if err != nil { + return fmt.Errorf("could not start tx: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + } + defer rollback() + + propertyValueRepo := property_value_repo.New(tx) + var toAdd []uuid.UUID + if valueChange.SingleSelectValue != nil { + parsedID, err := uuid.Parse(*valueChange.SingleSelectValue) + if err != nil { + return err, hwutil.PtrTo(esdb.NackActionPark) } - // add new connection - args := hwutil.Map(toAdd, func(id uuid.UUID) property_value_repo.ConnectValueWithSelectOptionsParams { - return property_value_repo.ConnectValueWithSelectOptionsParams{ - ValueID: evt.AggregateID, - SelectOption: id, - } - }) - if err := hwdb.ExecBatch(propertyValueRepo.ConnectValueWithSelectOptions(ctx, args)); err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not connect select options: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + toAdd = []uuid.UUID{parsedID} + + // delete any other connection + err = propertyValueRepo.DisconnectValueFromAllSelectOptions(ctx, aggregateID) + if err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not disconnectvalue from all options: %w", err), hwutil.PtrTo(esdb.NackActionRetry) } + } else if valueChange.MultiSelectValues != nil { + val := *valueChange.MultiSelectValues - // update consistency - err = propertyValueRepo.UpdatePropertyValueByID(ctx, property_value_repo.UpdatePropertyValueByIDParams{ - ID: evt.AggregateID, - Consistency: int64(evt.GetVersion()), - }) - if err := hwdb.Error(ctx, err); err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not update consistency of value: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + toAdd, err = hwutil.StringsToUUIDs(val.SelectValues) + if err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not parse SelectValues: %w", err), hwutil.PtrTo(esdb.NackActionPark) + } + + // delete requested options + if len(val.RemoveSelectValues) != 0 { + arr, err := hwutil.MapWithErr(val.RemoveSelectValues, func(s string) (property_value_repo.DisconnectValueFromSelectOptionsParams, error) { + id, err := uuid.Parse(s) + if err != nil { + return property_value_repo.DisconnectValueFromSelectOptionsParams{}, err + } + return property_value_repo.DisconnectValueFromSelectOptionsParams{ + ValueID: aggregateID, + SelectOption: id, + }, nil + }) + if err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not parse RemoveSelectValues: %w", err), hwutil.PtrTo(esdb.NackActionPark) + } + if err := hwdb.ExecBatch(propertyValueRepo.DisconnectValueFromSelectOptions(ctx, arr)); err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not disconnect desired options: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + } } + } - if err := tx.Commit(ctx); err != nil { - return fmt.Errorf("onPropertyValueUpdated: could not commit tx: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + // add new connection + args := hwutil.Map(toAdd, func(id uuid.UUID) property_value_repo.ConnectValueWithSelectOptionsParams { + return property_value_repo.ConnectValueWithSelectOptionsParams{ + ValueID: evt.AggregateID, + SelectOption: id, } - } else { - return updateBasicPropertyValue(ctx, evt, p.propertyValueRepo, fieldType, evt.AggregateID, payload) + }) + if err := hwdb.ExecBatch(propertyValueRepo.ConnectValueWithSelectOptions(ctx, args)); err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not connect select options: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + } + + // update consistency + err = propertyValueRepo.UpdatePropertyValueByID(ctx, property_value_repo.UpdatePropertyValueByIDParams{ + ID: evt.AggregateID, + Consistency: int64(evt.GetVersion()), + }) + if err := hwdb.Error(ctx, err); err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not update consistency of value: %w", err), hwutil.PtrTo(esdb.NackActionRetry) + } + + if err := tx.Commit(ctx); err != nil { + return fmt.Errorf("onPropertyValueUpdated: could not commit tx: %w", err), hwutil.PtrTo(esdb.NackActionRetry) } return nil, nil } // updateBasicPropertyValue is meant to be called in onPropertyValueUpdated -func updateBasicPropertyValue(ctx context.Context, evt hwes.Event, repo *property_value_repo.Queries, fieldType pb.FieldType, aggregateID uuid.UUID, payload propertyValueEventsV1.PropertyValueUpdatedEvent) (error, *esdb.NackAction) { +func updateBasicPropertyValue(ctx context.Context, evt hwes.Event, repo *property_value_repo.Queries, valueChange models.TypedValueChange) (error, *esdb.NackAction) { + aggregateID := evt.AggregateID + updatePropertyValueParams := property_value_repo.UpdatePropertyValueByIDParams{ ID: aggregateID, Consistency: int64(evt.GetVersion()), } - switch { - case fieldType == pb.FieldType_FIELD_TYPE_TEXT: - updatePropertyValueParams.TextValue = hwutil.PtrTo(fmt.Sprintf("%v", payload.Value)) - case fieldType == pb.FieldType_FIELD_TYPE_NUMBER: - val, ok := payload.Value.(float64) - if !ok { - return fmt.Errorf("could not assert number"), hwutil.PtrTo(esdb.NackActionPark) - } - updatePropertyValueParams.NumberValue = &val - case fieldType == pb.FieldType_FIELD_TYPE_CHECKBOX: - val, ok := payload.Value.(bool) - if !ok { - return fmt.Errorf("could not assert bool"), hwutil.PtrTo(esdb.NackActionPark) - } - updatePropertyValueParams.BoolValue = &val - case fieldType == pb.FieldType_FIELD_TYPE_DATE: - val, err := hwutil.AssertTimestampToTime(payload.Value) - if err != nil { - return err, hwutil.PtrTo(esdb.NackActionPark) - } - updatePropertyValueParams.DateValue = hwdb.TimeToDate(*val) - case fieldType == pb.FieldType_FIELD_TYPE_DATE_TIME: - val, err := hwutil.AssertTimestampToTime(payload.Value) - if err != nil { - return err, hwutil.PtrTo(esdb.NackActionPark) - } - updatePropertyValueParams.DateTimeValue = hwdb.TimeToTimestamp(*val) - default: - return fmt.Errorf("updateBasicPropertyValue called with fieldType %s", fieldType.String()), hwutil.PtrTo(esdb.NackActionPark) + if done := valueChange.SetBasicValues(&updatePropertyValueParams); !done { + return fmt.Errorf("updateBasicPropertyValue: could not set setBasicFromChange: %v", valueChange), hwutil.PtrTo(esdb.NackActionPark) } err := repo.UpdatePropertyValueByID(ctx, updatePropertyValueParams) diff --git a/services/property-svc/internal/property/projections/property_postgres_projection/property_postgres_projection.go b/services/property-svc/internal/property/projections/property_postgres_projection/property_postgres_projection.go index c6f83a6c1..7474510f9 100644 --- a/services/property-svc/internal/property/projections/property_postgres_projection/property_postgres_projection.go +++ b/services/property-svc/internal/property/projections/property_postgres_projection/property_postgres_projection.go @@ -53,6 +53,11 @@ func (p *Projection) initEventListeners() { func (p *Projection) onPropertyCreated(ctx context.Context, evt hwes.Event) (error, *esdb.NackAction) { log := zlog.Ctx(ctx) + organizationID := evt.CommitterOrganizationID + if organizationID == nil { + return fmt.Errorf("onPropertyCreated event is missing organizationID"), hwutil.PtrTo(esdb.NackActionSkip) + } + var payload propertyEventsV1.PropertyCreatedEvent if err := evt.GetJsonData(&payload); err != nil { log.Error().Err(err).Msg("unmarshal failed") @@ -78,11 +83,12 @@ func (p *Projection) onPropertyCreated(ctx context.Context, evt hwes.Event) (err fieldType := (pb.FieldType)(value) err = p.propertyRepo.CreateProperty(ctx, property_repo.CreatePropertyParams{ - ID: propertyID, - SubjectType: int32(subjectType), - FieldType: int32(fieldType), - Name: payload.Name, - Consistency: int64(evt.GetVersion()), + ID: propertyID, + SubjectType: int32(subjectType), + FieldType: int32(fieldType), + Name: payload.Name, + Consistency: int64(evt.GetVersion()), + OrganizationID: *organizationID, }) if err := hwdb.Error(ctx, err); err != nil { return err, hwutil.PtrTo(esdb.NackActionRetry) diff --git a/services/property-svc/internal/property/queries/v1/get_properties_by_subject_type.go b/services/property-svc/internal/property/queries/v1/get_properties_by_subject_type.go index 97da5e403..2cccefb9f 100644 --- a/services/property-svc/internal/property/queries/v1/get_properties_by_subject_type.go +++ b/services/property-svc/internal/property/queries/v1/get_properties_by_subject_type.go @@ -3,6 +3,7 @@ package v1 import ( "common" "context" + "fmt" pb "gen/services/property_svc/v1" "github.com/google/uuid" "hwdb" @@ -17,13 +18,19 @@ func NewGetPropertiesQueryHandler() GetPropertiesQueryHandler { return func(ctx context.Context, subjectType *pb.SubjectType) ([]*models.PropertyWithConsistency, error) { propertyRepo := property_repo.New(hwdb.GetDB()) + organizationID, err := common.GetOrganizationID(ctx) + if err != nil { + return nil, fmt.Errorf("GetPropertiesQueryHandler called without organization in context") + } + var subjectTypeID *int32 if subjectType != nil { subjectTypeID = hwutil.PtrTo(int32(*subjectType)) } rows, err := propertyRepo.GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrID(ctx, property_repo.GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDParams{ - SubjectType: subjectTypeID, + SubjectType: subjectTypeID, + OrganizationID: organizationID, }) if err := hwdb.Error(ctx, err); err != nil { return nil, err diff --git a/services/property-svc/migrations/000009_properties_organization.down.sql b/services/property-svc/migrations/000009_properties_organization.down.sql new file mode 100644 index 000000000..3a959ca3c --- /dev/null +++ b/services/property-svc/migrations/000009_properties_organization.down.sql @@ -0,0 +1 @@ +ALTER TABLE properties DROP COLUMN IF EXISTS organization_id; diff --git a/services/property-svc/migrations/000009_properties_organization.up.sql b/services/property-svc/migrations/000009_properties_organization.up.sql new file mode 100644 index 000000000..51abe5bdc --- /dev/null +++ b/services/property-svc/migrations/000009_properties_organization.up.sql @@ -0,0 +1,2 @@ +DELETE FROM properties; -- remove all rows, adding a new NOT NULL column without default +ALTER TABLE properties ADD COLUMN IF NOT EXISTS organization_id uuid NOT NULL; diff --git a/services/property-svc/repos/patient_views_repo/models.go b/services/property-svc/repos/patient_views_repo/models.go index 975bc7ac3..27133a465 100644 --- a/services/property-svc/repos/patient_views_repo/models.go +++ b/services/property-svc/repos/patient_views_repo/models.go @@ -21,15 +21,16 @@ type PatientPropertyViewRule struct { } type Property struct { - ID uuid.UUID - SubjectType int32 - FieldType int32 - Name string - Description string - IsArchived bool - SetID uuid.NullUUID - SelectDataID uuid.NullUUID - Consistency int64 + ID uuid.UUID + SubjectType int32 + FieldType int32 + Name string + Description string + IsArchived bool + SetID uuid.NullUUID + SelectDataID uuid.NullUUID + Consistency int64 + OrganizationID uuid.UUID } type PropertyValue struct { diff --git a/services/property-svc/repos/property_repo.sql b/services/property-svc/repos/property_repo.sql index 31a01a53b..cb14e7259 100644 --- a/services/property-svc/repos/property_repo.sql +++ b/services/property-svc/repos/property_repo.sql @@ -1,7 +1,7 @@ -- name: CreateProperty :exec INSERT INTO properties - (id, subject_type, field_type, name, consistency) -VALUES ($1, $2, $3, $4, $5); + (id, subject_type, field_type, name, consistency, organization_id) +VALUES ($1, $2, $3, $4, $5, $6); -- name: GetPropertyById :one SELECT * FROM properties WHERE id = $1; @@ -24,7 +24,9 @@ SELECT WHERE (subject_type = sqlc.narg('subject_type') OR sqlc.narg('subject_type') IS NULL ) AND - (properties.id = sqlc.narg('id') OR sqlc.narg('id') IS NULL); + (properties.id = sqlc.narg('id') OR sqlc.narg('id') IS NULL) + AND + properties.organization_id = @organization_id; -- name: UpdateProperty :exec UPDATE properties diff --git a/services/property-svc/repos/property_repo/models.go b/services/property-svc/repos/property_repo/models.go index 28a1f1ceb..dbc6ded18 100644 --- a/services/property-svc/repos/property_repo/models.go +++ b/services/property-svc/repos/property_repo/models.go @@ -21,15 +21,16 @@ type PatientPropertyViewRule struct { } type Property struct { - ID uuid.UUID - SubjectType int32 - FieldType int32 - Name string - Description string - IsArchived bool - SetID uuid.NullUUID - SelectDataID uuid.NullUUID - Consistency int64 + ID uuid.UUID + SubjectType int32 + FieldType int32 + Name string + Description string + IsArchived bool + SetID uuid.NullUUID + SelectDataID uuid.NullUUID + Consistency int64 + OrganizationID uuid.UUID } type PropertyValue struct { diff --git a/services/property-svc/repos/property_repo/property_repo.sql.go b/services/property-svc/repos/property_repo/property_repo.sql.go index ba34214fe..4f27c8107 100644 --- a/services/property-svc/repos/property_repo/property_repo.sql.go +++ b/services/property-svc/repos/property_repo/property_repo.sql.go @@ -13,16 +13,17 @@ import ( const createProperty = `-- name: CreateProperty :exec INSERT INTO properties - (id, subject_type, field_type, name, consistency) -VALUES ($1, $2, $3, $4, $5) + (id, subject_type, field_type, name, consistency, organization_id) +VALUES ($1, $2, $3, $4, $5, $6) ` type CreatePropertyParams struct { - ID uuid.UUID - SubjectType int32 - FieldType int32 - Name string - Consistency int64 + ID uuid.UUID + SubjectType int32 + FieldType int32 + Name string + Consistency int64 + OrganizationID uuid.UUID } func (q *Queries) CreateProperty(ctx context.Context, arg CreatePropertyParams) error { @@ -32,6 +33,7 @@ func (q *Queries) CreateProperty(ctx context.Context, arg CreatePropertyParams) arg.FieldType, arg.Name, arg.Consistency, + arg.OrganizationID, ) return err } @@ -77,7 +79,7 @@ func (q *Queries) CreateSelectOption(ctx context.Context, arg CreateSelectOption const getPropertiesWithSelectDataAndOptionsBySubjectTypeOrID = `-- name: GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrID :many SELECT - properties.id, properties.subject_type, properties.field_type, properties.name, properties.description, properties.is_archived, properties.set_id, properties.select_data_id, properties.consistency, + properties.id, properties.subject_type, properties.field_type, properties.name, properties.description, properties.is_archived, properties.set_id, properties.select_data_id, properties.consistency, properties.organization_id, select_options.id as select_option_id, select_options.name as select_option_name, select_options.description as select_option_description, @@ -91,11 +93,14 @@ SELECT (subject_type = $1 OR $1 IS NULL ) AND (properties.id = $2 OR $2 IS NULL) + AND + properties.organization_id = $3 ` type GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDParams struct { - SubjectType *int32 - ID uuid.NullUUID + SubjectType *int32 + ID uuid.NullUUID + OrganizationID uuid.UUID } type GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDRow struct { @@ -109,7 +114,7 @@ type GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDRow struct { } func (q *Queries) GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrID(ctx context.Context, arg GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDParams) ([]GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDRow, error) { - rows, err := q.db.Query(ctx, getPropertiesWithSelectDataAndOptionsBySubjectTypeOrID, arg.SubjectType, arg.ID) + rows, err := q.db.Query(ctx, getPropertiesWithSelectDataAndOptionsBySubjectTypeOrID, arg.SubjectType, arg.ID, arg.OrganizationID) if err != nil { return nil, err } @@ -127,6 +132,7 @@ func (q *Queries) GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrID(ctx con &i.Property.SetID, &i.Property.SelectDataID, &i.Property.Consistency, + &i.Property.OrganizationID, &i.SelectOptionID, &i.SelectOptionName, &i.SelectOptionDescription, @@ -145,7 +151,7 @@ func (q *Queries) GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrID(ctx con } const getPropertyById = `-- name: GetPropertyById :one -SELECT id, subject_type, field_type, name, description, is_archived, set_id, select_data_id, consistency FROM properties WHERE id = $1 +SELECT id, subject_type, field_type, name, description, is_archived, set_id, select_data_id, consistency, organization_id FROM properties WHERE id = $1 ` func (q *Queries) GetPropertyById(ctx context.Context, id uuid.UUID) (Property, error) { @@ -161,6 +167,7 @@ func (q *Queries) GetPropertyById(ctx context.Context, id uuid.UUID) (Property, &i.SetID, &i.SelectDataID, &i.Consistency, + &i.OrganizationID, ) return i, err } diff --git a/services/property-svc/repos/property_value_repo.sql b/services/property-svc/repos/property_value_repo.sql index 9aaea314b..5018068c6 100644 --- a/services/property-svc/repos/property_value_repo.sql +++ b/services/property-svc/repos/property_value_repo.sql @@ -22,18 +22,10 @@ DELETE FROM multi_select_values WHERE value_id = $1 AND select_option = $2; -- name: DisconnectValueFromAllSelectOptions :exec DELETE FROM multi_select_values WHERE value_id = $1; --- name: GetPropertyValueBySubjectIDAndPropertyID :many -SELECT - sqlc.embed(values), - so.id as select_option_id, - so.name as select_option_name, - so.description as select_option_description, - properties.field_type as field_type -FROM property_values as values - JOIN properties ON properties.id = values.property_id - LEFT JOIN multi_select_values as msv ON msv.value_id = values.id - LEFT JOIN select_options as so ON so.id = msv.select_option -WHERE (subject_id = @subject_id AND property_id = @property_id); +-- name: GetPropertyValueBySubjectIDAndPropertyID :one +SELECT id +FROM property_values +WHERE (subject_id = @subject_id AND property_id = @property_id) OR id = @id; -- name: UpdatePropertyValueByID :exec UPDATE property_values diff --git a/services/property-svc/repos/property_value_repo/interfaces.go b/services/property-svc/repos/property_value_repo/interfaces.go new file mode 100644 index 000000000..164f3b79f --- /dev/null +++ b/services/property-svc/repos/property_value_repo/interfaces.go @@ -0,0 +1,52 @@ +package property_value_repo + +import "github.com/jackc/pgx/v5/pgtype" + +// This is a handwritten file, +// we should open a PR to generate {s/g}etters in sqlc + +// Implement models.BasicChangeSettable + +// CreateBasicPropertyValueParams + +func (c *CreateBasicPropertyValueParams) SetTextValue(s *string) { + c.TextValue = s +} + +func (c *CreateBasicPropertyValueParams) SetNumberValue(f *float64) { + c.NumberValue = f +} + +func (c *CreateBasicPropertyValueParams) SetBoolValue(b *bool) { + c.BoolValue = b +} + +func (c *CreateBasicPropertyValueParams) SetDateValue(d pgtype.Date) { + c.DateValue = d +} + +func (c *CreateBasicPropertyValueParams) SetDateTimeValue(t pgtype.Timestamp) { + c.DateTimeValue = t +} + +// UpdatePropertyValueByIDParams + +func (u *UpdatePropertyValueByIDParams) SetTextValue(s *string) { + u.TextValue = s +} + +func (u *UpdatePropertyValueByIDParams) SetNumberValue(f *float64) { + u.NumberValue = f +} + +func (u *UpdatePropertyValueByIDParams) SetBoolValue(b *bool) { + u.BoolValue = b +} + +func (u *UpdatePropertyValueByIDParams) SetDateValue(d pgtype.Date) { + u.DateValue = d +} + +func (u *UpdatePropertyValueByIDParams) SetDateTimeValue(t pgtype.Timestamp) { + u.DateTimeValue = t +} diff --git a/services/property-svc/repos/property_value_repo/models.go b/services/property-svc/repos/property_value_repo/models.go index 2f7cdac21..fca9b6ae9 100644 --- a/services/property-svc/repos/property_value_repo/models.go +++ b/services/property-svc/repos/property_value_repo/models.go @@ -21,15 +21,16 @@ type PatientPropertyViewRule struct { } type Property struct { - ID uuid.UUID - SubjectType int32 - FieldType int32 - Name string - Description string - IsArchived bool - SetID uuid.NullUUID - SelectDataID uuid.NullUUID - Consistency int64 + ID uuid.UUID + SubjectType int32 + FieldType int32 + Name string + Description string + IsArchived bool + SetID uuid.NullUUID + SelectDataID uuid.NullUUID + Consistency int64 + OrganizationID uuid.UUID } type PropertyValue struct { diff --git a/services/property-svc/repos/property_value_repo/property_value_repo.sql.go b/services/property-svc/repos/property_value_repo/property_value_repo.sql.go index 22b0289a1..9552da7c1 100644 --- a/services/property-svc/repos/property_value_repo/property_value_repo.sql.go +++ b/services/property-svc/repos/property_value_repo/property_value_repo.sql.go @@ -94,65 +94,23 @@ func (q *Queries) GetPropertyValueByID(ctx context.Context, id uuid.UUID) (Prope return i, err } -const getPropertyValueBySubjectIDAndPropertyID = `-- name: GetPropertyValueBySubjectIDAndPropertyID :many -SELECT - values.id, values.property_id, values.subject_id, values.text_value, values.number_value, values.bool_value, values.date_value, values.date_time_value, values.consistency, - so.id as select_option_id, - so.name as select_option_name, - so.description as select_option_description, - properties.field_type as field_type -FROM property_values as values - JOIN properties ON properties.id = values.property_id - LEFT JOIN multi_select_values as msv ON msv.value_id = values.id - LEFT JOIN select_options as so ON so.id = msv.select_option -WHERE (subject_id = $1 AND property_id = $2) +const getPropertyValueBySubjectIDAndPropertyID = `-- name: GetPropertyValueBySubjectIDAndPropertyID :one +SELECT id +FROM property_values +WHERE (subject_id = $1 AND property_id = $2) OR id = $3 ` type GetPropertyValueBySubjectIDAndPropertyIDParams struct { SubjectID uuid.UUID PropertyID uuid.UUID + ID uuid.UUID } -type GetPropertyValueBySubjectIDAndPropertyIDRow struct { - PropertyValue PropertyValue - SelectOptionID uuid.NullUUID - SelectOptionName *string - SelectOptionDescription *string - FieldType int32 -} - -func (q *Queries) GetPropertyValueBySubjectIDAndPropertyID(ctx context.Context, arg GetPropertyValueBySubjectIDAndPropertyIDParams) ([]GetPropertyValueBySubjectIDAndPropertyIDRow, error) { - rows, err := q.db.Query(ctx, getPropertyValueBySubjectIDAndPropertyID, arg.SubjectID, arg.PropertyID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetPropertyValueBySubjectIDAndPropertyIDRow{} - for rows.Next() { - var i GetPropertyValueBySubjectIDAndPropertyIDRow - if err := rows.Scan( - &i.PropertyValue.ID, - &i.PropertyValue.PropertyID, - &i.PropertyValue.SubjectID, - &i.PropertyValue.TextValue, - &i.PropertyValue.NumberValue, - &i.PropertyValue.BoolValue, - &i.PropertyValue.DateValue, - &i.PropertyValue.DateTimeValue, - &i.PropertyValue.Consistency, - &i.SelectOptionID, - &i.SelectOptionName, - &i.SelectOptionDescription, - &i.FieldType, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil +func (q *Queries) GetPropertyValueBySubjectIDAndPropertyID(ctx context.Context, arg GetPropertyValueBySubjectIDAndPropertyIDParams) (uuid.UUID, error) { + row := q.db.QueryRow(ctx, getPropertyValueBySubjectIDAndPropertyID, arg.SubjectID, arg.PropertyID, arg.ID) + var id uuid.UUID + err := row.Scan(&id) + return id, err } const getPropertyValuesWithPropertyBySubjectID = `-- name: GetPropertyValuesWithPropertyBySubjectID :many @@ -218,7 +176,7 @@ func (q *Queries) GetPropertyValuesWithPropertyBySubjectID(ctx context.Context, const getRelevantPropertyViews = `-- name: GetRelevantPropertyViews :many SELECT - properties.id, properties.subject_type, properties.field_type, properties.name, properties.description, properties.is_archived, properties.set_id, properties.select_data_id, properties.consistency, + properties.id, properties.subject_type, properties.field_type, properties.name, properties.description, properties.is_archived, properties.set_id, properties.select_data_id, properties.consistency, properties.organization_id, values.id as value_id, values.text_value, values.bool_value, @@ -279,6 +237,7 @@ func (q *Queries) GetRelevantPropertyViews(ctx context.Context, arg GetRelevantP &i.Property.SetID, &i.Property.SelectDataID, &i.Property.Consistency, + &i.Property.OrganizationID, &i.ValueID, &i.TextValue, &i.BoolValue, diff --git a/services/property-svc/repos/task_views_repo/models.go b/services/property-svc/repos/task_views_repo/models.go index ca784ca3f..42ffa230f 100644 --- a/services/property-svc/repos/task_views_repo/models.go +++ b/services/property-svc/repos/task_views_repo/models.go @@ -21,15 +21,16 @@ type PatientPropertyViewRule struct { } type Property struct { - ID uuid.UUID - SubjectType int32 - FieldType int32 - Name string - Description string - IsArchived bool - SetID uuid.NullUUID - SelectDataID uuid.NullUUID - Consistency int64 + ID uuid.UUID + SubjectType int32 + FieldType int32 + Name string + Description string + IsArchived bool + SetID uuid.NullUUID + SelectDataID uuid.NullUUID + Consistency int64 + OrganizationID uuid.UUID } type PropertyValue struct { diff --git a/services/property-svc/repos/views_repo/models.go b/services/property-svc/repos/views_repo/models.go index c6551859d..d21e8c01a 100644 --- a/services/property-svc/repos/views_repo/models.go +++ b/services/property-svc/repos/views_repo/models.go @@ -21,15 +21,16 @@ type PatientPropertyViewRule struct { } type Property struct { - ID uuid.UUID - SubjectType int32 - FieldType int32 - Name string - Description string - IsArchived bool - SetID uuid.NullUUID - SelectDataID uuid.NullUUID - Consistency int64 + ID uuid.UUID + SubjectType int32 + FieldType int32 + Name string + Description string + IsArchived bool + SetID uuid.NullUUID + SelectDataID uuid.NullUUID + Consistency int64 + OrganizationID uuid.UUID } type PropertyValue struct { diff --git a/services/property-svc/schema.sql b/services/property-svc/schema.sql index caaa1fe9b..6aea45e0e 100644 --- a/services/property-svc/schema.sql +++ b/services/property-svc/schema.sql @@ -97,7 +97,8 @@ CREATE TABLE public.properties ( is_archived boolean DEFAULT false NOT NULL, set_id uuid, select_data_id uuid, - consistency bigint DEFAULT 0 NOT NULL + consistency bigint DEFAULT 0 NOT NULL, + organization_id uuid NOT NULL ); diff --git a/services/property-svc/stories/GetPropertieBySubjectType_test.go b/services/property-svc/stories/GetPropertieBySubjectType_test.go index dafdae386..6295019ea 100644 --- a/services/property-svc/stories/GetPropertieBySubjectType_test.go +++ b/services/property-svc/stories/GetPropertieBySubjectType_test.go @@ -16,7 +16,8 @@ import ( // - Create Properties // - Check GetPropertiesBySubjectType func TestGetProperties(t *testing.T) { - propertyClient := propertyServiceClient() + orgID := uuid.New().String() + propertyClient := pb.NewPropertyServiceClient(hwtesting.GetGrpcConn("", orgID)) ctx := context.Background() diff --git a/services/property-svc/stories/PropertyValueCRUD_test.go b/services/property-svc/stories/PropertyValueCRUD_test.go index 69cff7c83..e1986026a 100644 --- a/services/property-svc/stories/PropertyValueCRUD_test.go +++ b/services/property-svc/stories/PropertyValueCRUD_test.go @@ -12,7 +12,7 @@ import ( // TestCreateAttachUpdateTextProperty: // - Create a Property, -// - Attach a Value +// - Attach a ValueChange // - Update said value func TestCreateAttachUpdateTextProperty(t *testing.T) { propertyClient := propertyServiceClient() @@ -92,7 +92,7 @@ func TestCreateAttachUpdateTextProperty(t *testing.T) { SubjectId: subjectId, PropertyId: propertyID.String(), Value: &pb.AttachPropertyValueRequest_TextValue{ - TextValue: "Initial Text Value", + TextValue: "Initial Text ValueChange", }, }) @@ -122,19 +122,19 @@ func TestCreateAttachUpdateTextProperty(t *testing.T) { assert.Equal(t, 1, len(attachedValuesResponse.Values)) - assert.Equal(t, "Initial Text Value", attachedValuesResponse.Values[0].GetTextValue()) + assert.Equal(t, "Initial Text ValueChange", attachedValuesResponse.Values[0].GetTextValue()) assert.Equal(t, &attachResponse.Consistency, attachedValuesResponse.Values[0].ValueConsistency) // - // Update Value + // Update ValueChange // updateResponse, err := valueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ SubjectId: subjectId, PropertyId: propertyID.String(), Value: &pb.AttachPropertyValueRequest_TextValue{ - TextValue: "Updated Text Value", + TextValue: "Updated Text ValueChange", }, }) @@ -165,14 +165,14 @@ func TestCreateAttachUpdateTextProperty(t *testing.T) { assert.Equal(t, 1, len(attachedValuesResponse.Values)) - assert.Equal(t, "Updated Text Value", attachedValuesResponse.Values[0].GetTextValue()) + assert.Equal(t, "Updated Text ValueChange", attachedValuesResponse.Values[0].GetTextValue()) assert.Equal(t, &updateResponse.Consistency, attachedValuesResponse.Values[0].ValueConsistency, "ValueConsistency was not updated") } // TestCreateAttachUpdateSelectProperty: // - Create a Property, -// - Attach a Value +// - Attach a ValueChange // - Update said value func TestCreateAttachUpdateSelectProperty(t *testing.T) { propertyClient := propertyServiceClient() @@ -304,7 +304,7 @@ func TestCreateAttachUpdateSelectProperty(t *testing.T) { assert.Equal(t, &attachResponse.Consistency, attachedValuesResponse.Values[0].ValueConsistency) // - // Update Value + // Update ValueChange // updateResponse, err := valueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ @@ -348,7 +348,7 @@ func TestCreateAttachUpdateSelectProperty(t *testing.T) { // TestCreateAttachUpdateMultiSelectProperty: // - Create a Property, -// - Attach a Value +// - Attach a ValueChange // - Update said value func TestCreateAttachUpdateMultiSelectProperty(t *testing.T) { propertyClient := propertyServiceClient() @@ -488,7 +488,7 @@ func TestCreateAttachUpdateMultiSelectProperty(t *testing.T) { assert.Equal(t, &attachResponse.Consistency, attachedValuesResponse.Values[0].ValueConsistency) // - // Update Value + // Update ValueChange // updateResponse, err := valueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ @@ -536,9 +536,9 @@ func TestCreateAttachUpdateMultiSelectProperty(t *testing.T) { // TestCreateAttachAddOptionAttachSelectProperty: // - Create a Property, -// - Attach a Value +// - Attach a ValueChange // - Upsert an Option -// - Attach another Value +// - Attach another ValueChange func TestCreateAttachAddOptionAttachSelectProperty(t *testing.T) { propertyClient := propertyServiceClient() ctx := context.Background() @@ -671,7 +671,7 @@ func TestCreateAttachAddOptionAttachSelectProperty(t *testing.T) { option2 := propertyResponse.GetSelectData().Options[1].GetId() // not guaranteed tbf // - // Update Value + // Update ValueChange // _, err = valueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ @@ -711,9 +711,9 @@ func TestCreateAttachAddOptionAttachSelectProperty(t *testing.T) { // TestCreateAttachAddOptionAttachMultiSelectProperty: // - Create a Property, -// - Attach a Value +// - Attach a ValueChange // - Upsert an Option -// - Attach another Value +// - Attach another ValueChange func TestCreateAttachAddOptionAttachMultiSelectProperty(t *testing.T) { propertyClient := propertyServiceClient() ctx := context.Background() @@ -850,7 +850,7 @@ func TestCreateAttachAddOptionAttachMultiSelectProperty(t *testing.T) { option2 := propertyResponse.GetSelectData().Options[1].GetId() // order not guaranteed // - // Update Value + // Update ValueChange // _, err = valueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ diff --git a/services/property-svc/stories/setup_test.go b/services/property-svc/stories/setup_test.go index f9545da6d..d30e8d68c 100644 --- a/services/property-svc/stories/setup_test.go +++ b/services/property-svc/stories/setup_test.go @@ -57,15 +57,15 @@ func TestMain(m *testing.M) { } func propertyServiceClient() pb.PropertyServiceClient { - return pb.NewPropertyServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewPropertyServiceClient(hwtesting.GetGrpcConn("", "")) } func propertyViewServiceClient() pb.PropertyViewsServiceClient { - return pb.NewPropertyViewsServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewPropertyViewsServiceClient(hwtesting.GetGrpcConn("", "")) } func propertyValueServiceClient() pb.PropertyValueServiceClient { - return pb.NewPropertyValueServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewPropertyValueServiceClient(hwtesting.GetGrpcConn("", "")) } func NamesOf(arr []*pb.SelectValueOption) []string { diff --git a/services/tasks-svc/stories/PatientCRUD_test.go b/services/tasks-svc/stories/PatientCRUD_test.go index ac12f9d62..600a1480b 100644 --- a/services/tasks-svc/stories/PatientCRUD_test.go +++ b/services/tasks-svc/stories/PatientCRUD_test.go @@ -627,7 +627,7 @@ func TestGetPatientDetails(t *testing.T) { func TestGetRecentPatients(t *testing.T) { userID := uuid.New() // new user for this test, to prevent interference with other tests - patientClient := pb.NewPatientServiceClient(hwtesting.GetGrpcConn(userID.String())) + patientClient := pb.NewPatientServiceClient(hwtesting.GetGrpcConn(userID.String(), "")) ctx := context.Background() wardId, _ := prepareWard(t, ctx, "") diff --git a/services/tasks-svc/stories/TaskCRUD_test.go b/services/tasks-svc/stories/TaskCRUD_test.go index cf5ac16dd..b02294eb8 100644 --- a/services/tasks-svc/stories/TaskCRUD_test.go +++ b/services/tasks-svc/stories/TaskCRUD_test.go @@ -370,7 +370,7 @@ func TestGetAssignedTasks(t *testing.T) { hwtesting.WaitForProjectionsToSettle() // client for userid - customTaskClient := pb.NewTaskServiceClient(hwtesting.GetGrpcConn(userID.String())) + customTaskClient := pb.NewTaskServiceClient(hwtesting.GetGrpcConn(userID.String(), "")) res, err := customTaskClient.GetAssignedTasks(ctx, &pb.GetAssignedTasksRequest{}) assert.NoError(t, err) diff --git a/services/tasks-svc/stories/WardCRUD_test.go b/services/tasks-svc/stories/WardCRUD_test.go index d39b7477e..3e895c8d2 100644 --- a/services/tasks-svc/stories/WardCRUD_test.go +++ b/services/tasks-svc/stories/WardCRUD_test.go @@ -85,9 +85,9 @@ func prepareWards(t *testing.T, ctx context.Context, client pb.WardServiceClient func TestGetRecentWards(t *testing.T) { userID := uuid.New() // new user for this test, to prevent interference with other tests - wardClient := pb.NewWardServiceClient(hwtesting.GetGrpcConn(userID.String())) - taskClient := pb.NewTaskServiceClient(hwtesting.GetGrpcConn(userID.String())) - patientClient := pb.NewPatientServiceClient(hwtesting.GetGrpcConn(userID.String())) + wardClient := pb.NewWardServiceClient(hwtesting.GetGrpcConn(userID.String(), "")) + taskClient := pb.NewTaskServiceClient(hwtesting.GetGrpcConn(userID.String(), "")) + patientClient := pb.NewPatientServiceClient(hwtesting.GetGrpcConn(userID.String(), "")) ctx := context.Background() wardIds := prepareWards(t, ctx, wardClient, 11) diff --git a/services/tasks-svc/stories/setup_test.go b/services/tasks-svc/stories/setup_test.go index ed84780df..2136d3917 100644 --- a/services/tasks-svc/stories/setup_test.go +++ b/services/tasks-svc/stories/setup_test.go @@ -70,26 +70,26 @@ func TestMain(m *testing.M) { } func bedServiceClient() pb.BedServiceClient { - return pb.NewBedServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewBedServiceClient(hwtesting.GetGrpcConn("", "")) } func roomServiceClient() pb.RoomServiceClient { - return pb.NewRoomServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewRoomServiceClient(hwtesting.GetGrpcConn("", "")) } func patientServiceClient() pb.PatientServiceClient { - return pb.NewPatientServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewPatientServiceClient(hwtesting.GetGrpcConn("", "")) } func taskServiceClient() pb.TaskServiceClient { - return pb.NewTaskServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewTaskServiceClient(hwtesting.GetGrpcConn("", "")) } func taskTemplateServiceClient() pb.TaskTemplateServiceClient { - return pb.NewTaskTemplateServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewTaskTemplateServiceClient(hwtesting.GetGrpcConn("", "")) } func wardServiceClient() pb.WardServiceClient { - return pb.NewWardServiceClient(hwtesting.GetGrpcConn("")) + return pb.NewWardServiceClient(hwtesting.GetGrpcConn("", "")) } func prepareWard(t *testing.T, ctx context.Context, suffix string) (wardID, wardConsistency string) { From 95e382f22993337f6472da342aa055105749cbab Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 10:18:48 +0200 Subject: [PATCH 25/42] let the debugging begin --- libs/hwutil/arrays.go | 26 +++++ .../internal/property-value/api/grpc.go | 25 +++-- .../property-value/models/propertyValue.go | 95 ++++++++++++++++++- 3 files changed, 138 insertions(+), 8 deletions(-) diff --git a/libs/hwutil/arrays.go b/libs/hwutil/arrays.go index 53a27b512..edd4ddcf0 100644 --- a/libs/hwutil/arrays.go +++ b/libs/hwutil/arrays.go @@ -186,3 +186,29 @@ func Without[T comparable](original, itemsToRemove []T) []T { return result } + +// SameItems yields true, iff a and b have the same elements (order may vary!) +func SameItems[T comparable](a, b []T) bool { + if len(a) != len(b) { + return false + } + + A := make(map[T]bool) + for _, item := range a { + A[item] = true + } + + B := make(map[T]bool) + for _, item := range b { + B[item] = true + } + + // Compare the maps + for key, in := range A { + if B[key] != in { + return false + } + } + + return true +} diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 79ceba9f7..43ed13fcf 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -155,14 +155,27 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req conflicts := make(map[string]*commonpb.AttributeConflict) // TODO: find a generic approach - valueUpdateRequested := false // TODO valueChange != conflict.Is.Value - valueAlreadyUpdated := conflict.Was.Value != conflict.Is.Value - if valueUpdateRequested && valueAlreadyUpdated { + valueUpdateRequested := valueChange.DoesChange(conflict.Was.Value) + valueAlreadyUpdated := func() bool { + if conflict.Is == nil && conflict.Was == nil { + return false + } + if conflict.Is == nil || conflict.Was == nil { + return true + } + return !conflict.Is.Value.Equals(*conflict.Was.Value) + } + if valueUpdateRequested && valueAlreadyUpdated() { var is proto.Message + if conflict.Is.Value != nil { + is = conflict.Is.Value.ToProtoMessage() + } var want proto.Message - // TODO: isValue := conflict.Is.Value - - // TODO + if conflict.Was.Value != nil { + val := *conflict.Was.Value + valueChange.Apply(&val) // we modify values also pointed to by WAS here, which will be thrown away after this anyway, so it's fine + want = conflict.Was.Value.ToProtoMessage() + } conflicts["value"], err = util.AttributeConflict( is, diff --git a/services/property-svc/internal/property-value/models/propertyValue.go b/services/property-svc/internal/property-value/models/propertyValue.go index 828fe6f1b..2982c74d5 100644 --- a/services/property-svc/internal/property-value/models/propertyValue.go +++ b/services/property-svc/internal/property-value/models/propertyValue.go @@ -1,9 +1,15 @@ package models import ( + "fmt" + commonpb "gen/libs/common/v1" pb "gen/services/property_svc/v1" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" "hwutil" "time" @@ -47,6 +53,54 @@ type SimpleTypedValue struct { MultiSelectValues []string } +func (v SimpleTypedValue) Equals(o SimpleTypedValue) bool { + switch { + case v.TextValue != nil && o.TextValue != nil: + return *v.TextValue == *o.TextValue + case v.BoolValue != nil && o.BoolValue != nil: + return *v.BoolValue == *o.BoolValue + case v.NumberValue != nil && o.NumberValue != nil: + return *v.NumberValue == *o.NumberValue + case v.DateTimeValue != nil && o.DateTimeValue != nil: + return *v.DateTimeValue == *o.DateTimeValue + case v.DateValue != nil && o.DateValue != nil: + return *v.DateValue == *o.DateValue + case v.SingleSelectValue != nil && o.SingleSelectValue != nil: + return *v.SingleSelectValue == *o.SingleSelectValue + case v.MultiSelectValues != nil && o.MultiSelectValues != nil: + return hwutil.SameItems(v.MultiSelectValues, o.MultiSelectValues) + default: + return false + } +} + +func (v SimpleTypedValue) ToProtoMessage() proto.Message { + switch { + case v.TextValue != nil: + return wrapperspb.String(*v.TextValue) + case v.BoolValue != nil: + return wrapperspb.Bool(*v.BoolValue) + case v.NumberValue != nil: + return wrapperspb.Double(*v.NumberValue) + case v.DateTimeValue != nil: + return timestamppb.New(*v.DateTimeValue) + case v.DateValue != nil: + return &pb.Date{Date: timestamppb.New(*v.DateValue)} + case v.SingleSelectValue != nil: + return wrapperspb.String(*v.SingleSelectValue) + case v.MultiSelectValues != nil: + els, err := hwutil.MapWithErr(v.MultiSelectValues, func(o string) (*anypb.Any, error) { + return anypb.New(wrapperspb.String(o)) + }) + if err != nil { + panic("SimpleTypedValue.ToProtoMessage: could not create anypb of string") + } + return &commonpb.AnyArray{Elements: els} + default: + panic(fmt.Sprintf("SimpleTypedValue.ToProtoMessage: unhandled state: %v", v)) + } +} + type TypedValue struct { TextValue *string BoolValue *bool @@ -90,6 +144,13 @@ type TypedValueChange struct { MultiSelectValues *MultiSelectChange `json:"multi_select_values,omitempty"` } +func applyMultiSelectValueChange(options []string, change MultiSelectChange) []string { + return append( + hwutil.Without(options, change.RemoveSelectValues), // remove first + change.SelectValues..., // add new options second + ) +} + // Apply applies a TypedValueChange to a TypedValue (except removal) func (c TypedValueChange) Apply(value *SimpleTypedValue) bool { switch { @@ -106,8 +167,7 @@ func (c TypedValueChange) Apply(value *SimpleTypedValue) bool { case c.SingleSelectValue != nil: value.SingleSelectValue = c.SingleSelectValue case c.MultiSelectValues != nil: - value.MultiSelectValues = hwutil.Without(value.MultiSelectValues, c.MultiSelectValues.RemoveSelectValues) - value.MultiSelectValues = append(value.MultiSelectValues, c.MultiSelectValues.SelectValues...) + value.MultiSelectValues = applyMultiSelectValueChange(value.MultiSelectValues, *c.MultiSelectValues) default: return false @@ -142,3 +202,34 @@ func (c TypedValueChange) SetBasicValues(settable BasicChangeSettable) bool { return true } + +func (c TypedValueChange) DoesChange(value *SimpleTypedValue) bool { + if value == nil { + return !c.ValueRemoved + } + + switch { + case c.ValueRemoved: + return false // no conflict possible + case c.TextValue != nil: + return value.TextValue != nil && *c.TextValue != *value.TextValue + case c.BoolValue != nil: + return value.BoolValue != nil && *c.BoolValue != *value.BoolValue + case c.NumberValue != nil: + return value.NumberValue != nil && *c.NumberValue != *value.NumberValue + case c.DateValue != nil: + return value.DateValue != nil && *c.DateValue != *value.DateValue + case c.DateTimeValue != nil: + return value.DateTimeValue != nil && *c.DateTimeValue != *value.DateTimeValue + case c.SingleSelectValue != nil: + return value.SingleSelectValue != nil && *c.SingleSelectValue != *value.SingleSelectValue + case c.MultiSelectValues != nil: + if value.MultiSelectValues == nil { + return false + } + applied := applyMultiSelectValueChange(value.MultiSelectValues, *c.MultiSelectValues) + return !hwutil.SameItems(value.MultiSelectValues, applied) + default: + panic(fmt.Sprintf("TypedValueChange.DoesChange: value unknown: %v", c)) + } +} From 7a5b55390950d49c235ba4871a282899db543a7c Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 10:20:50 +0200 Subject: [PATCH 26/42] lets not forget this --- gen/go/services/property_svc/v1/types.pb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/go/services/property_svc/v1/types.pb.go b/gen/go/services/property_svc/v1/types.pb.go index 519df131e..48a7f41dc 100644 --- a/gen/go/services/property_svc/v1/types.pb.go +++ b/gen/go/services/property_svc/v1/types.pb.go @@ -140,7 +140,7 @@ type Date struct { unknownFields protoimpl.UnknownFields // information more precise than date information shall be disregarded by clients - Date *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=date,proto3" json:"date,omitempty"` + Date *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=date,proto3" json:"date,omitempty" validate:"required"` // @gotags: validate:"required" } func (x *Date) Reset() { From baee2078b231195105e99290a531f29844b22198 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 10:33:08 +0200 Subject: [PATCH 27/42] nice --- services/property-svc/go.mod | 1 + services/property-svc/go.sum | 2 ++ .../internal/property-value/aggregate/aggregate.go | 8 +++++--- .../property-svc/internal/property-value/api/grpc.go | 10 +++++++--- .../stories/AttachPropertyValueConflict_test.go | 6 ++++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/services/property-svc/go.mod b/services/property-svc/go.mod index 589aeb459..53911fe29 100644 --- a/services/property-svc/go.mod +++ b/services/property-svc/go.mod @@ -73,6 +73,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/services/property-svc/go.sum b/services/property-svc/go.sum index a1aabd316..a84c739f7 100644 --- a/services/property-svc/go.sum +++ b/services/property-svc/go.sum @@ -122,6 +122,8 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas= diff --git a/services/property-svc/internal/property-value/aggregate/aggregate.go b/services/property-svc/internal/property-value/aggregate/aggregate.go index dda0967f2..4aa37b10e 100644 --- a/services/property-svc/internal/property-value/aggregate/aggregate.go +++ b/services/property-svc/internal/property-value/aggregate/aggregate.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "github.com/google/uuid" + "github.com/jinzhu/copier" "hwes" propertyEventsV1 "property-svc/internal/property-value/events/v1" "property-svc/internal/property-value/models" @@ -50,9 +51,10 @@ func LoadPropertyValueAggregateWithSnapshotAt(ctx context.Context, as hwes.Aggre return nil, nil, err } - cpy := *property.PropertyValue // deref does a top-level copy - // well see how it deals with .ValueChange, of type interface (TODO) - + var cpy models.PropertyValue + if err := copier.Copy(&cpy, property.PropertyValue); err != nil { + return nil, nil, fmt.Errorf("LoadPropertyValueAggregateWithSnapshotAt: could not copy snapshot: %w", err) + } snapshot = &cpy } diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 43ed13fcf..a6e99b0ae 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -7,6 +7,7 @@ import ( commonpb "gen/libs/common/v1" pb "gen/services/property_svc/v1" "github.com/google/uuid" + "github.com/jinzhu/copier" zlog "github.com/rs/zerolog/log" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -172,9 +173,12 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req } var want proto.Message if conflict.Was.Value != nil { - val := *conflict.Was.Value - valueChange.Apply(&val) // we modify values also pointed to by WAS here, which will be thrown away after this anyway, so it's fine - want = conflict.Was.Value.ToProtoMessage() + var val models.SimpleTypedValue + if err := copier.Copy(&val, conflict.Was.Value); err != nil { + return nil, fmt.Errorf("could not copy was to want: %w", err) + } + valueChange.Apply(&val) + want = val.ToProtoMessage() } conflicts["value"], err = util.AttributeConflict( diff --git a/services/property-svc/stories/AttachPropertyValueConflict_test.go b/services/property-svc/stories/AttachPropertyValueConflict_test.go index 4e85e17fd..93ae0c34f 100644 --- a/services/property-svc/stories/AttachPropertyValueConflict_test.go +++ b/services/property-svc/stories/AttachPropertyValueConflict_test.go @@ -75,7 +75,9 @@ func TestAttachPropertyValueConflict(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, wantRes.Conflict) - // TODO - assert.Equal(t, "TODO", strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) + s := "conflicting_attributes:{key:\"value\" value:{is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"TestAttachPropertyValueConflict IS\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"TestAttachPropertyValueConflict WANT\"}}}}" + + assert.Equal(t, s, strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) } From 55654de6b0a1febe75247bd061fd6afe1b3018fc Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 10:43:51 +0200 Subject: [PATCH 28/42] fixies --- libs/hwtesting/env.go | 2 +- .../internal/property/queries/v1/get_property_by_id.go | 8 +++++++- services/property-svc/stories/GetProperty_test.go | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libs/hwtesting/env.go b/libs/hwtesting/env.go index c8e2b90d5..5c6116679 100644 --- a/libs/hwtesting/env.go +++ b/libs/hwtesting/env.go @@ -12,7 +12,7 @@ func SetCommonEnv() { errs = append(errs, os.Setenv("LOG_LEVEL", "trace")) errs = append(errs, os.Setenv("INSECURE_FAKE_TOKEN_ENABLE", "true")) errs = append(errs, os.Setenv("INSECURE_REDIS_NO_TLS", "true")) - errs = append(errs, os.Setenv("ORGANIZATION_ID", "3b25c6f5-4705-4074-9fc6-a50c28eba405")) + errs = append(errs, os.Setenv("ORGANIZATION_ID", "3b25c6f5-4705-4074-9fc6-a50c28eba406")) for _, err := range errs { if err != nil { diff --git a/services/property-svc/internal/property/queries/v1/get_property_by_id.go b/services/property-svc/internal/property/queries/v1/get_property_by_id.go index bde4e29ca..2e8dbaf32 100644 --- a/services/property-svc/internal/property/queries/v1/get_property_by_id.go +++ b/services/property-svc/internal/property/queries/v1/get_property_by_id.go @@ -17,8 +17,14 @@ func NewGetPropertyByIDQueryHandler() GetPropertyByIDQueryHandler { return func(ctx context.Context, propertyID uuid.UUID) (*models.Property, common.ConsistencyToken, error) { propertyRepo := property_repo.New(hwdb.GetDB()) + organizationID, err := common.GetOrganizationID(ctx) + if err != nil { + return nil, 0, err + } + rows, err := propertyRepo.GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrID(ctx, property_repo.GetPropertiesWithSelectDataAndOptionsBySubjectTypeOrIDParams{ - ID: uuid.NullUUID{UUID: propertyID, Valid: true}, + ID: uuid.NullUUID{UUID: propertyID, Valid: true}, + OrganizationID: organizationID, }) if err := hwdb.Error(ctx, err); err != nil { return nil, 0, err diff --git a/services/property-svc/stories/GetProperty_test.go b/services/property-svc/stories/GetProperty_test.go index 541ed6d32..6f548e2ab 100644 --- a/services/property-svc/stories/GetProperty_test.go +++ b/services/property-svc/stories/GetProperty_test.go @@ -45,7 +45,6 @@ func TestTaskGetPropertyAlwaysIncluded(t *testing.T) { if !assert.NoError(t, err, "propertyID is not a uuid") { return } - hwtesting.WaitForProjectionsToSettle() // From 6d834187575f6202fc515524739d2cacd86f8a55 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 10:57:30 +0200 Subject: [PATCH 29/42] copier --- .../property-value/aggregate/aggregate.go | 2 +- .../internal/property-value/api/grpc.go | 2 +- .../internal/property/aggregate/aggregate.go | 24 ++++--------------- services/tasks-svc/go.mod | 1 + services/tasks-svc/go.sum | 2 ++ .../internal/patient/aggregate/aggregate.go | 9 +++++-- .../internal/task/aggregate/aggregate.go | 18 ++++---------- 7 files changed, 22 insertions(+), 36 deletions(-) diff --git a/services/property-svc/internal/property-value/aggregate/aggregate.go b/services/property-svc/internal/property-value/aggregate/aggregate.go index 4aa37b10e..b78288f51 100644 --- a/services/property-svc/internal/property-value/aggregate/aggregate.go +++ b/services/property-svc/internal/property-value/aggregate/aggregate.go @@ -52,7 +52,7 @@ func LoadPropertyValueAggregateWithSnapshotAt(ctx context.Context, as hwes.Aggre } var cpy models.PropertyValue - if err := copier.Copy(&cpy, property.PropertyValue); err != nil { + if err := copier.CopyWithOption(&cpy, property.PropertyValue, copier.Option{DeepCopy: true}); err != nil { return nil, nil, fmt.Errorf("LoadPropertyValueAggregateWithSnapshotAt: could not copy snapshot: %w", err) } snapshot = &cpy diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index a6e99b0ae..97af13c85 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -174,7 +174,7 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req var want proto.Message if conflict.Was.Value != nil { var val models.SimpleTypedValue - if err := copier.Copy(&val, conflict.Was.Value); err != nil { + if err := copier.CopyWithOption(&val, conflict.Was.Value, copier.Option{DeepCopy: true}); err != nil { return nil, fmt.Errorf("could not copy was to want: %w", err) } valueChange.Apply(&val) diff --git a/services/property-svc/internal/property/aggregate/aggregate.go b/services/property-svc/internal/property/aggregate/aggregate.go index 91b9d3f66..208b846da 100644 --- a/services/property-svc/internal/property/aggregate/aggregate.go +++ b/services/property-svc/internal/property/aggregate/aggregate.go @@ -6,6 +6,7 @@ import ( "fmt" pb "gen/services/property_svc/v1" "github.com/google/uuid" + "github.com/jinzhu/copier" "hwes" "hwutil" propertyEventsV1 "property-svc/internal/property/events/v1" @@ -43,26 +44,11 @@ func LoadPropertyAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateS return nil, nil, err } - propertyCopy := *property.Property // deref copies model - - // copy pointer values - propertyCopy.FieldTypeData = models.FieldTypeData{} - - sd := property.Property.FieldTypeData.SelectData - if sd != nil { - propertyCopy.FieldTypeData.SelectData = &models.SelectData{ - AllowFreetext: sd.AllowFreetext, - SelectOptions: make([]models.SelectOption, 0), - } - - for _, option := range sd.SelectOptions { - if option.Description != nil { - option.Description = hwutil.PtrTo(*option.Description) - } - propertyCopy.FieldTypeData.SelectData.SelectOptions = append(propertyCopy.FieldTypeData.SelectData.SelectOptions, option) - } + var cpy models.Property + if err := copier.CopyWithOption(&cpy, property.Property, copier.Option{DeepCopy: true}); err != nil { + return nil, nil, fmt.Errorf("LoadPropertyAggregateWithSnapshotAt: could not copy snapshot: %w", err) } - snapshot = &propertyCopy + snapshot = &cpy } // continue loading all other events diff --git a/services/tasks-svc/go.mod b/services/tasks-svc/go.mod index 8ff76d048..83c3cb568 100644 --- a/services/tasks-svc/go.mod +++ b/services/tasks-svc/go.mod @@ -85,6 +85,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/jzelinskie/stringz v0.0.3 // indirect github.com/klauspost/compress v1.17.9 // indirect diff --git a/services/tasks-svc/go.sum b/services/tasks-svc/go.sum index a00b131b8..15f63d86a 100644 --- a/services/tasks-svc/go.sum +++ b/services/tasks-svc/go.sum @@ -149,6 +149,8 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas= diff --git a/services/tasks-svc/internal/patient/aggregate/aggregate.go b/services/tasks-svc/internal/patient/aggregate/aggregate.go index db83d071d..a7a0f2bbd 100644 --- a/services/tasks-svc/internal/patient/aggregate/aggregate.go +++ b/services/tasks-svc/internal/patient/aggregate/aggregate.go @@ -3,7 +3,9 @@ package aggregate import ( "common" "context" + "fmt" "github.com/google/uuid" + "github.com/jinzhu/copier" "hwes" patientEventsV1 "tasks-svc/internal/patient/events/v1" "tasks-svc/internal/patient/models" @@ -42,8 +44,11 @@ func LoadPatientAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateSt return nil, nil, err } - patientCopy := *patientAggregate.Patient // deref copies model - snapshot = &patientCopy + var cpy models.Patient + if err := copier.CopyWithOption(&cpy, patientAggregate.Patient, copier.Option{DeepCopy: true}); err != nil { + return nil, nil, fmt.Errorf("LoadPatientAggregateWithSnapshotAt: could not copy snapshot: %w", err) + } + snapshot = &cpy } // continue loading all other events diff --git a/services/tasks-svc/internal/task/aggregate/aggregate.go b/services/tasks-svc/internal/task/aggregate/aggregate.go index 9d2590e6d..f746de336 100644 --- a/services/tasks-svc/internal/task/aggregate/aggregate.go +++ b/services/tasks-svc/internal/task/aggregate/aggregate.go @@ -6,8 +6,8 @@ import ( "fmt" pb "gen/services/tasks_svc/v1" "github.com/google/uuid" + "github.com/jinzhu/copier" "hwes" - "hwutil" taskEventsV1 "tasks-svc/internal/task/events/v1" "tasks-svc/internal/task/models" "time" @@ -51,19 +51,11 @@ func LoadTaskAggregateWithSnapshotAt(ctx context.Context, as hwes.AggregateStore return nil, nil, err } - task := *taskAggregate.Task // deref copies model - - // also copy pointer values - if task.DueAt != nil { - task.DueAt = hwutil.PtrTo(*task.DueAt) - } - subtasks := make(map[uuid.UUID]models.Subtask) - for key, value := range task.Subtasks { - subtasks[key] = value + var cpy models.Task + if err := copier.CopyWithOption(&cpy, taskAggregate.Task, copier.Option{DeepCopy: true}); err != nil { + return nil, nil, fmt.Errorf("LoadTaskAggregateWithSnapshotAt: could not copy snapshot: %w", err) } - task.Subtasks = subtasks - - snapshot = &task + snapshot = &cpy } // continue loading all other events From cd5a9cfc8b7211a61b53255001f84f34b5f08300 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 11:17:56 +0200 Subject: [PATCH 30/42] coolsies --- .../internal/property-value/api/grpc.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 97af13c85..77f1d89f3 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -80,33 +80,33 @@ func toTypedValueChange(value pb.IsAttachPropertyValueRequest_Value) (typedValue ValueRemoved: true, } } - switch value.(type) { + switch value := value.(type) { case *pb.AttachPropertyValueRequest_TextValue: return &models.TypedValueChange{ - TextValue: &value.(*pb.AttachPropertyValueRequest_TextValue).TextValue, + TextValue: &value.TextValue, } case *pb.AttachPropertyValueRequest_NumberValue: return &models.TypedValueChange{ - NumberValue: &value.(*pb.AttachPropertyValueRequest_NumberValue).NumberValue, + NumberValue: &value.NumberValue, } case *pb.AttachPropertyValueRequest_BoolValue: return &models.TypedValueChange{ - BoolValue: &value.(*pb.AttachPropertyValueRequest_BoolValue).BoolValue, + BoolValue: &value.BoolValue, } case *pb.AttachPropertyValueRequest_DateValue: return &models.TypedValueChange{ - DateValue: hwutil.PtrTo(value.(*pb.AttachPropertyValueRequest_DateValue).DateValue.Date.AsTime()), + DateValue: hwutil.PtrTo(value.DateValue.Date.AsTime()), } case *pb.AttachPropertyValueRequest_DateTimeValue: return &models.TypedValueChange{ - DateTimeValue: hwutil.PtrTo(value.(*pb.AttachPropertyValueRequest_DateTimeValue).DateTimeValue.AsTime()), + DateTimeValue: hwutil.PtrTo(value.DateTimeValue.AsTime()), } case *pb.AttachPropertyValueRequest_SelectValue: return &models.TypedValueChange{ - SingleSelectValue: &value.(*pb.AttachPropertyValueRequest_SelectValue).SelectValue, + SingleSelectValue: &value.SelectValue, } case *pb.AttachPropertyValueRequest_MultiSelectValue_: - msv := value.(*pb.AttachPropertyValueRequest_MultiSelectValue_).MultiSelectValue + msv := value.MultiSelectValue return &models.TypedValueChange{ MultiSelectValues: &models.MultiSelectChange{ SelectValues: msv.SelectValues, From 80b9e21d264b033dc9ae62ddae08d86eab789fbb Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 11:22:17 +0200 Subject: [PATCH 31/42] fix --- libs/common/grpc.go | 6 +++++- libs/hwes/event_test.go | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/libs/common/grpc.go b/libs/common/grpc.go index 5550d3a3f..6e1b6bfe8 100644 --- a/libs/common/grpc.go +++ b/libs/common/grpc.go @@ -231,6 +231,10 @@ func ContextWithUserID(ctx context.Context, userID uuid.UUID) context.Context { return context.WithValue(ctx, userIDKey{}, userID) } +func ContextWithOrganizationID(ctx context.Context, organizationID uuid.UUID) context.Context { + return context.WithValue(ctx, organizationIDKey{}, organizationID) +} + // handleOrganizationIDForAuthFunc is a part of our auth middleware. // The claims are signed. Therefore, we can match the user provided // organizationID from the headers against the organizationIDs inside the claim. @@ -268,7 +272,7 @@ func handleOrganizationIDForAuthFunc(ctx context.Context) (context.Context, erro return nil, status.Errorf(codes.Unauthenticated, "no access to this organization") } - ctx = context.WithValue(ctx, organizationIDKey{}, organizationID) + ctx = ContextWithOrganizationID(ctx, organizationID) // Append organizationID to the logger loggerWithOrganizationID := log.With().Str("organizationID", organizationID.String()).Logger() diff --git a/libs/hwes/event_test.go b/libs/hwes/event_test.go index 12c2ccde9..24fa3dc1f 100644 --- a/libs/hwes/event_test.go +++ b/libs/hwes/event_test.go @@ -4,6 +4,7 @@ import ( "common" "context" "github.com/google/uuid" + "github.com/stretchr/testify/assert" "hwes" "testing" ) @@ -11,15 +12,16 @@ import ( func TestEventWithUserID(t *testing.T) { ctx := context.Background() u := uuid.New() + o := uuid.New() e := hwes.Event{} ctx = common.ContextWithUserID(ctx, u) + ctx = common.ContextWithOrganizationID(ctx, o) if err := e.SetCommitterFromCtx(ctx); err != nil { t.Error(err) } - if *e.CommitterUserID != u { - t.Error("event does not have the correct CommitterUserID") - } + assert.Equal(t, u, *e.CommitterUserID) + assert.Equal(t, o, *e.CommitterOrganizationID) } From fad2cec26e3421b79fc7c6860c796de6f78f25d8 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 11:36:28 +0200 Subject: [PATCH 32/42] adjust tests after merge --- libs/hwtesting/env.go | 1 - libs/hwtesting/grpc.go | 31 ++++++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/libs/hwtesting/env.go b/libs/hwtesting/env.go index 5c6116679..3eaf578d3 100644 --- a/libs/hwtesting/env.go +++ b/libs/hwtesting/env.go @@ -12,7 +12,6 @@ func SetCommonEnv() { errs = append(errs, os.Setenv("LOG_LEVEL", "trace")) errs = append(errs, os.Setenv("INSECURE_FAKE_TOKEN_ENABLE", "true")) errs = append(errs, os.Setenv("INSECURE_REDIS_NO_TLS", "true")) - errs = append(errs, os.Setenv("ORGANIZATION_ID", "3b25c6f5-4705-4074-9fc6-a50c28eba406")) for _, err := range errs { if err != nil { diff --git a/libs/hwtesting/grpc.go b/libs/hwtesting/grpc.go index e1c341e79..c85dab93d 100644 --- a/libs/hwtesting/grpc.go +++ b/libs/hwtesting/grpc.go @@ -42,26 +42,31 @@ func (t InsecureBearerToken) RequireTransportSecurity() bool { const FakeTokenUser = "18159713-5d4e-4ad5-94ad-fbb6bb147984" func GetFakeTokenCredentials(subOverride, orgOverride string) InsecureBearerToken { + sub := FakeTokenUser + if subOverride != "" { + sub = subOverride + } + + org := "3b25c6f5-4705-4074-9fc6-a50c28eba406" + if orgOverride != "" { + org = orgOverride + } + // README's fake token m := map[string]interface{}{ - "sub": FakeTokenUser, - "email": "testine.test@helpwave.de", - "name": "Testine Test", - "preferred_username": "testine.test", + "sub": sub, + "email": "max.mustermann@helpwave.de", + "email_verified": true, + "name": "Max Mustermann", + "preferred_username": "max.mustermann", + "given_name": "Max", + "family_name": "Mustermann", "organization": map[string]interface{}{ - "id": "3b25c6f5-4705-4074-9fc6-a50c28eba406", + "id": org, "name": "helpwave test", }, } - if subOverride != "" { - m["sub"] = subOverride - } - - if orgOverride != "" { - m["organizations"] = []string{orgOverride} - } - bytes, err := json.Marshal(m) if err != nil { panic(fmt.Errorf("GetFakeTokenCredentials failed: %w", err)) From 9212bad3a85d09398416d9a58065bcabbf83ce9f Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 13:46:42 +0200 Subject: [PATCH 33/42] more tests --- .../internal/property-value/api/grpc.go | 2 +- .../property-value/models/propertyValue.go | 10 +- .../AttachPropertyValueConflict_test.go | 346 +++++++++++++++++- 3 files changed, 347 insertions(+), 11 deletions(-) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 77f1d89f3..4c50dd23c 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -156,7 +156,7 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req conflicts := make(map[string]*commonpb.AttributeConflict) // TODO: find a generic approach - valueUpdateRequested := valueChange.DoesChange(conflict.Was.Value) + valueUpdateRequested := valueChange.ConflictPossible(conflict.Was.Value) valueAlreadyUpdated := func() bool { if conflict.Is == nil && conflict.Was == nil { return false diff --git a/services/property-svc/internal/property-value/models/propertyValue.go b/services/property-svc/internal/property-value/models/propertyValue.go index 2982c74d5..21cf42bb8 100644 --- a/services/property-svc/internal/property-value/models/propertyValue.go +++ b/services/property-svc/internal/property-value/models/propertyValue.go @@ -203,7 +203,7 @@ func (c TypedValueChange) SetBasicValues(settable BasicChangeSettable) bool { return true } -func (c TypedValueChange) DoesChange(value *SimpleTypedValue) bool { +func (c TypedValueChange) ConflictPossible(value *SimpleTypedValue) bool { if value == nil { return !c.ValueRemoved } @@ -224,12 +224,8 @@ func (c TypedValueChange) DoesChange(value *SimpleTypedValue) bool { case c.SingleSelectValue != nil: return value.SingleSelectValue != nil && *c.SingleSelectValue != *value.SingleSelectValue case c.MultiSelectValues != nil: - if value.MultiSelectValues == nil { - return false - } - applied := applyMultiSelectValueChange(value.MultiSelectValues, *c.MultiSelectValues) - return !hwutil.SameItems(value.MultiSelectValues, applied) + return false // no conflict possible default: - panic(fmt.Sprintf("TypedValueChange.DoesChange: value unknown: %v", c)) + panic(fmt.Sprintf("TypedValueChange.ConflictPossible: value unknown: %v", c)) } } diff --git a/services/property-svc/stories/AttachPropertyValueConflict_test.go b/services/property-svc/stories/AttachPropertyValueConflict_test.go index 93ae0c34f..cc7f227e6 100644 --- a/services/property-svc/stories/AttachPropertyValueConflict_test.go +++ b/services/property-svc/stories/AttachPropertyValueConflict_test.go @@ -5,12 +5,15 @@ import ( pb "gen/services/property_svc/v1" "github.com/google/uuid" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" "hwtesting" + "strconv" "strings" "testing" + "time" ) -func TestAttachPropertyValueConflict(t *testing.T) { +func TestAttachPropertyValueConflict_Text(t *testing.T) { propertyClient := propertyServiceClient() propertyValueClient := propertyValueServiceClient() @@ -75,8 +78,345 @@ func TestAttachPropertyValueConflict(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, wantRes.Conflict) - s := "conflicting_attributes:{key:\"value\" value:{is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"TestAttachPropertyValueConflict IS\"}} " + - "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"TestAttachPropertyValueConflict WANT\"}}}}" + s := "conflicting_attributes:{key:\"value\" value:{is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + t.Name() + " IS\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + t.Name() + " WANT\"}}}}" + + assert.Equal(t, s, strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) + +} + +func TestAttachPropertyValueConflict_Number(t *testing.T) { + propertyClient := propertyServiceClient() + propertyValueClient := propertyValueServiceClient() + + ctx := context.Background() + + // Preparations + + createPropertyRequest := &pb.CreatePropertyRequest{ + SubjectType: pb.SubjectType_SUBJECT_TYPE_PATIENT, + FieldType: pb.FieldType_FIELD_TYPE_NUMBER, + Name: t.Name(), + Description: nil, + SetId: nil, + } + + createRes, err := propertyClient.CreateProperty(ctx, createPropertyRequest) + assert.NoError(t, err) + hwtesting.WaitForProjectionsToSettle() + + propertyID := createRes.PropertyId + subjectID := uuid.New().String() + + // WAS + + wasRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_NumberValue{ + NumberValue: 1.0, + }, + Consistency: nil, + }) + + assert.NoError(t, err) + assert.Nil(t, wasRes.Conflict) + + initialConsistency := wasRes.Consistency + + // IS + + isRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_NumberValue{ + NumberValue: 2.0, + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, isRes.Conflict) + + // WANT + + wantRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_NumberValue{ + NumberValue: 3.0, + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.NotNil(t, wantRes.Conflict) + + s := "conflicting_attributes:{key:\"value\" value:{is:{[type.googleapis.com/google.protobuf.DoubleValue]:{value:2}} " + + "want:{[type.googleapis.com/google.protobuf.DoubleValue]:{value:3}}}}" + + assert.Equal(t, s, strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) + +} + +func TestAttachPropertyValueConflict_Date(t *testing.T) { + propertyClient := propertyServiceClient() + propertyValueClient := propertyValueServiceClient() + + ctx := context.Background() + + // Preparations + + createPropertyRequest := &pb.CreatePropertyRequest{ + SubjectType: pb.SubjectType_SUBJECT_TYPE_PATIENT, + FieldType: pb.FieldType_FIELD_TYPE_DATE, + Name: t.Name(), + Description: nil, + SetId: nil, + } + + createRes, err := propertyClient.CreateProperty(ctx, createPropertyRequest) + assert.NoError(t, err) + hwtesting.WaitForProjectionsToSettle() + + propertyID := createRes.PropertyId + subjectID := uuid.New().String() + + // WAS + + was := time.Now().Round(time.Millisecond) + + wasRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_DateValue{ + DateValue: &pb.Date{Date: timestamppb.New(was)}, + }, + Consistency: nil, + }) + + assert.NoError(t, err) + assert.Nil(t, wasRes.Conflict) + + initialConsistency := wasRes.Consistency + hwtesting.WaitForProjectionsToSettle() + + // IS + + is := time.Now().Round(time.Millisecond) + + isRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_DateValue{ + DateValue: &pb.Date{Date: timestamppb.New(is)}, + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, isRes.Conflict) + hwtesting.WaitForProjectionsToSettle() + + // WANT + + want := time.Now().Round(time.Millisecond) + + wantRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_DateValue{ + DateValue: &pb.Date{Date: timestamppb.New(want)}, + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.NotNil(t, wantRes.Conflict) + + s := "conflicting_attributes:{key:\"value\" " + + "value:{is:{[type.googleapis.com/services.property_svc.v1.Date]:{date:{seconds:" + strconv.FormatInt(is.Unix(), 10) + " nanos:" + strconv.Itoa(is.Nanosecond()) + "}}} " + + "want:{[type.googleapis.com/services.property_svc.v1.Date]:{date:{seconds:" + strconv.FormatInt(want.Unix(), 10) + " nanos:" + strconv.Itoa(want.Nanosecond()) + "}}}}}" + + assert.Equal(t, s, strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) + +} + +func TestAttachPropertyValueConflict_DateTime(t *testing.T) { + propertyClient := propertyServiceClient() + propertyValueClient := propertyValueServiceClient() + + ctx := context.Background() + + // Preparations + + createPropertyRequest := &pb.CreatePropertyRequest{ + SubjectType: pb.SubjectType_SUBJECT_TYPE_PATIENT, + FieldType: pb.FieldType_FIELD_TYPE_DATE_TIME, + Name: t.Name(), + Description: nil, + SetId: nil, + } + + createRes, err := propertyClient.CreateProperty(ctx, createPropertyRequest) + assert.NoError(t, err) + hwtesting.WaitForProjectionsToSettle() + + propertyID := createRes.PropertyId + subjectID := uuid.New().String() + + // WAS + + was := time.Now().Round(time.Millisecond) + + wasRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_DateTimeValue{ + DateTimeValue: timestamppb.New(was), + }, + Consistency: nil, + }) + + assert.NoError(t, err) + assert.Nil(t, wasRes.Conflict) + + initialConsistency := wasRes.Consistency + hwtesting.WaitForProjectionsToSettle() + + // IS + + is := time.Now().Round(time.Millisecond) + + isRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_DateTimeValue{ + DateTimeValue: timestamppb.New(is), + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, isRes.Conflict) + hwtesting.WaitForProjectionsToSettle() + + // WANT + + want := time.Now().Round(time.Millisecond) + + wantRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_DateTimeValue{ + DateTimeValue: timestamppb.New(want), + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.NotNil(t, wantRes.Conflict) + + s := "conflicting_attributes:{key:\"value\" " + + "value:{is:{[type.googleapis.com/google.protobuf.Timestamp]:{seconds:" + strconv.FormatInt(is.Unix(), 10) + " nanos:" + strconv.Itoa(is.Nanosecond()) + "}} " + + "want:{[type.googleapis.com/google.protobuf.Timestamp]:{seconds:" + strconv.FormatInt(want.Unix(), 10) + " nanos:" + strconv.Itoa(want.Nanosecond()) + "}}}}" + + assert.Equal(t, s, strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) + +} + +func TestAttachPropertyValueConflict_SingleSelect(t *testing.T) { + propertyClient := propertyServiceClient() + propertyValueClient := propertyValueServiceClient() + + ctx := context.Background() + + // Preparations + + createPropertyRequest := &pb.CreatePropertyRequest{ + SubjectType: pb.SubjectType_SUBJECT_TYPE_PATIENT, + FieldType: pb.FieldType_FIELD_TYPE_SELECT, + Name: t.Name(), + Description: nil, + SetId: nil, + FieldTypeData: &pb.CreatePropertyRequest_SelectData_{ + SelectData: &pb.CreatePropertyRequest_SelectData{ + Options: []*pb.CreatePropertyRequest_SelectData_SelectOption{ + { + Name: t.Name() + " A", + Description: nil, + }, + { + Name: t.Name() + " B", + Description: nil, + }, + { + Name: t.Name() + " C", + Description: nil, + }, + }, + }, + }, + } + + createRes, err := propertyClient.CreateProperty(ctx, createPropertyRequest) + assert.NoError(t, err) + hwtesting.WaitForProjectionsToSettle() + + propertyID := createRes.PropertyId + subjectID := uuid.New().String() + + prop, err := propertyClient.GetProperty(ctx, &pb.GetPropertyRequest{ + Id: propertyID, + ViewSource: nil, + }) + assert.NoError(t, err) + opts := prop.GetSelectData().Options + was := opts[0].Id + is := opts[1].Id + want := opts[2].Id + + // WAS + + wasRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_SelectValue{ + SelectValue: was, + }, + Consistency: nil, + }) + + assert.NoError(t, err) + assert.Nil(t, wasRes.Conflict) + + initialConsistency := wasRes.Consistency + hwtesting.WaitForProjectionsToSettle() + + // IS + + isRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_SelectValue{ + SelectValue: is, + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.Nil(t, isRes.Conflict) + hwtesting.WaitForProjectionsToSettle() + + // WANT + + wantRes, err := propertyValueClient.AttachPropertyValue(ctx, &pb.AttachPropertyValueRequest{ + SubjectId: subjectID, + PropertyId: propertyID, + Value: &pb.AttachPropertyValueRequest_SelectValue{ + SelectValue: want, + }, + Consistency: &initialConsistency, + }) + assert.NoError(t, err) + assert.NotNil(t, wantRes.Conflict) + + s := "conflicting_attributes:{key:\"value\" " + + "value:{is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + is + "\"}} " + + "want:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + want + "\"}}}}" assert.Equal(t, s, strings.ReplaceAll(wantRes.Conflict.String(), " ", " ")) From 51c0a3cb95dfc5753dd552324d779a8bb087b7c1 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 14:41:26 +0200 Subject: [PATCH 34/42] one conflict type --- libs/common/conflict.go | 6 ++++++ .../commands/v1/attach_property_value.go | 11 +++-------- .../property-svc/internal/property/api/grpc.go | 4 ++-- .../property/commands/v1/update_property.go | 17 +++++------------ .../stories/AttachPropertyValueConflict_test.go | 1 + services/tasks-svc/internal/patient/api/grpc.go | 12 ++++++------ .../internal/patient/commands/v1/assign_bed.go | 17 +++++------------ .../patient/commands/v1/unassign_bed.go | 17 +++++------------ .../patient/commands/v1/update_patient.go | 17 +++++------------ services/tasks-svc/internal/task/api/grpc.go | 12 ++++++------ .../internal/task/commands/v1/assign_task.go | 12 ++++++------ .../internal/task/commands/v1/update_subtask.go | 12 ++++++------ .../internal/task/commands/v1/update_task.go | 17 +++++------------ 13 files changed, 61 insertions(+), 94 deletions(-) create mode 100644 libs/common/conflict.go diff --git a/libs/common/conflict.go b/libs/common/conflict.go new file mode 100644 index 000000000..c35f68c73 --- /dev/null +++ b/libs/common/conflict.go @@ -0,0 +1,6 @@ +package common + +type Conflict[T any] struct { + Is T + Was T +} diff --git a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go index 94a428984..31cb43534 100644 --- a/services/property-svc/internal/property-value/commands/v1/attach_property_value.go +++ b/services/property-svc/internal/property-value/commands/v1/attach_property_value.go @@ -17,15 +17,10 @@ type AttachPropertyValueCommandHandler func(ctx context.Context, valueChange models.TypedValueChange, subjectID uuid.UUID, expConsistency *common.ConsistencyToken, -) (common.ConsistencyToken, *AttachPropertyValueConflict, error) - -type AttachPropertyValueConflict struct { - Was *models.PropertyValue - Is *models.PropertyValue -} +) (common.ConsistencyToken, *common.Conflict[*models.PropertyValue], error) func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachPropertyValueCommandHandler { - return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, valueChange models.TypedValueChange, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AttachPropertyValueConflict, error) { + return func(ctx context.Context, propertyValueID uuid.UUID, propertyID uuid.UUID, valueChange models.TypedValueChange, subjectID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.PropertyValue], error) { propertyValueRepo := property_value_repo.New(hwdb.GetDB()) var a *aggregate.PropertyValueAggregate @@ -55,7 +50,7 @@ func NewAttachPropertyValueCommandHandler(as hwes.AggregateStore) AttachProperty // conflict detection consistency := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && consistency != *expConsistency { - return consistency, &AttachPropertyValueConflict{ + return consistency, &common.Conflict[*models.PropertyValue]{ Was: snapshot, Is: a.PropertyValue, }, err diff --git a/services/property-svc/internal/property/api/grpc.go b/services/property-svc/internal/property/api/grpc.go index 988da9196..e897e47cb 100644 --- a/services/property-svc/internal/property/api/grpc.go +++ b/services/property-svc/internal/property/api/grpc.go @@ -337,12 +337,12 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update if len(conflicts) != 0 { return &pb.UpdatePropertyResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: conflict.Consistency.String(), + Consistency: c.String(), }, nil } // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } return &pb.UpdatePropertyResponse{ diff --git a/services/property-svc/internal/property/commands/v1/update_property.go b/services/property-svc/internal/property/commands/v1/update_property.go index e8193b3de..8c31b5933 100644 --- a/services/property-svc/internal/property/commands/v1/update_property.go +++ b/services/property-svc/internal/property/commands/v1/update_property.go @@ -10,16 +10,10 @@ import ( "property-svc/internal/property/models" ) -type UpdatePropertyConflict struct { - Consistency common.ConsistencyToken - Was *models.Property - Is *models.Property -} - -type UpdatePropertyCommandHandler func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdatePropertyConflict, error) +type UpdatePropertyCommandHandler func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Property], error) func NewUpdatePropertyCommandHandler(as hwes.AggregateStore) UpdatePropertyCommandHandler { - return func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdatePropertyConflict, error) { + return func(ctx context.Context, propertyID uuid.UUID, subjectType *pb.SubjectType, name *string, description *string, setID *string, allowFreetext *bool, upsertOptions *[]models.UpdateSelectOption, removeOptions []string, isArchived *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Property], error) { a, oldState, err := aggregate.LoadPropertyAggregateWithSnapshotAt(ctx, as, propertyID, expConsistency) if err != nil { return 0, nil, err @@ -28,10 +22,9 @@ func NewUpdatePropertyCommandHandler(as hwes.AggregateStore) UpdatePropertyComma // update happened since? newToken := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && *expConsistency != newToken { - return 0, &UpdatePropertyConflict{ - Consistency: newToken, - Was: oldState, - Is: a.Property, + return newToken, &common.Conflict[*models.Property]{ + Was: oldState, + Is: a.Property, }, nil } diff --git a/services/property-svc/stories/AttachPropertyValueConflict_test.go b/services/property-svc/stories/AttachPropertyValueConflict_test.go index cc7f227e6..4cd2971ec 100644 --- a/services/property-svc/stories/AttachPropertyValueConflict_test.go +++ b/services/property-svc/stories/AttachPropertyValueConflict_test.go @@ -136,6 +136,7 @@ func TestAttachPropertyValueConflict_Number(t *testing.T) { }) assert.NoError(t, err) assert.Nil(t, isRes.Conflict) + hwtesting.WaitForProjectionsToSettle() // WANT diff --git a/services/tasks-svc/internal/patient/api/grpc.go b/services/tasks-svc/internal/patient/api/grpc.go index 652788ece..658079cc4 100644 --- a/services/tasks-svc/internal/patient/api/grpc.go +++ b/services/tasks-svc/internal/patient/api/grpc.go @@ -441,12 +441,12 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa if len(conflicts) != 0 { return &pb.UpdatePatientResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: common.ConsistencyToken(conflict.Consistency).String(), + Consistency: c.String(), }, nil } // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } tracking.AddPatientToRecentActivity(ctx, patientID.String()) @@ -516,12 +516,12 @@ func (s *PatientGrpcService) AssignBed(ctx context.Context, req *pb.AssignBedReq if len(conflicts) != 0 { return &pb.AssignBedResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: common.ConsistencyToken(conflict.Consistency).String(), + Consistency: common.ConsistencyToken(consistency).String(), }, nil } // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } log.Info().Str("patientID", patientID.String()).Str("bedID", bedID.String()).Msg("assigned bed to patient") @@ -581,12 +581,12 @@ func (s *PatientGrpcService) UnassignBed(ctx context.Context, req *pb.UnassignBe if len(conflicts) != 0 { return &pb.UnassignBedResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: conflict.Consistency.String(), + Consistency: c.String(), }, nil } // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } log.Info().Str("patientID", patientID.String()).Msg("unassigned bed from patient") diff --git a/services/tasks-svc/internal/patient/commands/v1/assign_bed.go b/services/tasks-svc/internal/patient/commands/v1/assign_bed.go index 7b1a47480..cc2ec59d4 100644 --- a/services/tasks-svc/internal/patient/commands/v1/assign_bed.go +++ b/services/tasks-svc/internal/patient/commands/v1/assign_bed.go @@ -9,16 +9,10 @@ import ( "tasks-svc/internal/patient/models" ) -type AssignBedConflict struct { - Consistency common.ConsistencyToken - Was *models.Patient - Is *models.Patient -} - -type AssignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AssignBedConflict, error) +type AssignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Patient], error) func NewAssignBedCommandHandler(as hwes.AggregateStore) AssignBedCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *AssignBedConflict, error) { + return func(ctx context.Context, patientID uuid.UUID, bedID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Patient], error) { a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expConsistency) if err != nil { return 0, nil, err @@ -27,10 +21,9 @@ func NewAssignBedCommandHandler(as hwes.AggregateStore) AssignBedCommandHandler // update happened since? newToken := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && *expConsistency != newToken { - return 0, &AssignBedConflict{ - Consistency: newToken, - Was: oldState, - Is: a.Patient, + return newToken, &common.Conflict[*models.Patient]{ + Was: oldState, + Is: a.Patient, }, nil } diff --git a/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go b/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go index f8c8af482..5bee5e2cb 100644 --- a/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go +++ b/services/tasks-svc/internal/patient/commands/v1/unassign_bed.go @@ -9,16 +9,10 @@ import ( "tasks-svc/internal/patient/models" ) -type UnassignBedConflict struct { - Consistency common.ConsistencyToken - Was *models.Patient - Is *models.Patient -} - -type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, expectedConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UnassignBedConflict, error) +type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, expectedConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Patient], error) func NewUnassignBedCommandHandler(as hwes.AggregateStore) UnassignBedCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, expectedConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UnassignBedConflict, error) { + return func(ctx context.Context, patientID uuid.UUID, expectedConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Patient], error) { a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expectedConsistency) if err != nil { return 0, nil, err @@ -27,10 +21,9 @@ func NewUnassignBedCommandHandler(as hwes.AggregateStore) UnassignBedCommandHand // update has happened since? newToken := common.ConsistencyToken(a.GetVersion()) if expectedConsistency != nil && *expectedConsistency != newToken { - return 0, &UnassignBedConflict{ - Consistency: newToken, - Was: oldState, - Is: a.Patient, + return newToken, &common.Conflict[*models.Patient]{ + Was: oldState, + Is: a.Patient, }, nil } diff --git a/services/tasks-svc/internal/patient/commands/v1/update_patient.go b/services/tasks-svc/internal/patient/commands/v1/update_patient.go index 7972efffe..01e6c9814 100644 --- a/services/tasks-svc/internal/patient/commands/v1/update_patient.go +++ b/services/tasks-svc/internal/patient/commands/v1/update_patient.go @@ -9,16 +9,10 @@ import ( "tasks-svc/internal/patient/models" ) -type UpdatePatientConflict struct { - Consistency common.ConsistencyToken - Was *models.Patient - Is *models.Patient -} - -type UpdatePatientCommandHandler func(ctx context.Context, patientID uuid.UUID, expConsistency *common.ConsistencyToken, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) +type UpdatePatientCommandHandler func(ctx context.Context, patientID uuid.UUID, expConsistency *common.ConsistencyToken, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *common.Conflict[*models.Patient], error) func NewUpdatePatientCommandHandler(as hwes.AggregateStore) UpdatePatientCommandHandler { - return func(ctx context.Context, patientID uuid.UUID, expConsistency *common.ConsistencyToken, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *UpdatePatientConflict, error) { + return func(ctx context.Context, patientID uuid.UUID, expConsistency *common.ConsistencyToken, humanReadableIdentifier, notes *string) (common.ConsistencyToken, *common.Conflict[*models.Patient], error) { a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expConsistency) if err != nil { return 0, nil, err @@ -27,10 +21,9 @@ func NewUpdatePatientCommandHandler(as hwes.AggregateStore) UpdatePatientCommand // was an update performed since expConsistency? newToken := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && *expConsistency != newToken { - return 0, &UpdatePatientConflict{ - Consistency: newToken, - Was: oldState, - Is: a.Patient, + return newToken, &common.Conflict[*models.Patient]{ + Was: oldState, + Is: a.Patient, }, nil } diff --git a/services/tasks-svc/internal/task/api/grpc.go b/services/tasks-svc/internal/task/api/grpc.go index ab04c0579..2dc66028f 100644 --- a/services/tasks-svc/internal/task/api/grpc.go +++ b/services/tasks-svc/internal/task/api/grpc.go @@ -170,12 +170,12 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ if len(conflicts) != 0 { return &pb.UpdateTaskResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: conflict.Consistency.String(), + Consistency: c.String(), }, nil } // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } return &pb.UpdateTaskResponse{ @@ -237,12 +237,12 @@ func (s *TaskGrpcService) AssignTask(ctx context.Context, req *pb.AssignTaskRequ if len(conflicts) != 0 { return &pb.AssignTaskResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - Consistency: conflict.Consistency.String(), + Consistency: c.String(), }, nil } // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } return &pb.AssignTaskResponse{ @@ -575,7 +575,7 @@ func (s *TaskGrpcService) UpdateSubtask(ctx context.Context, req *pb.UpdateSubta if len(conflicts) != 0 { return &pb.UpdateSubtaskResponse{ Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts}, - TaskConsistency: conflict.Consistency.String(), + TaskConsistency: c.String(), }, nil } @@ -584,7 +584,7 @@ func (s *TaskGrpcService) UpdateSubtask(ctx context.Context, req *pb.UpdateSubta // so either that is the case still, or the update will do nothing anyway // no conflict? retry with new consistency - expConsistency = &conflict.Consistency + expConsistency = &c } return &pb.UpdateSubtaskResponse{ diff --git a/services/tasks-svc/internal/task/commands/v1/assign_task.go b/services/tasks-svc/internal/task/commands/v1/assign_task.go index c1a777ee2..dc8ffe7e8 100644 --- a/services/tasks-svc/internal/task/commands/v1/assign_task.go +++ b/services/tasks-svc/internal/task/commands/v1/assign_task.go @@ -6,12 +6,13 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/task/aggregate" + "tasks-svc/internal/task/models" ) -type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) +type AssignTaskCommandHandler func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Task], error) func NewAssignTaskCommandHandler(as hwes.AggregateStore) AssignTaskCommandHandler { - return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) { + return func(ctx context.Context, taskID, userID uuid.UUID, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Task], error) { task, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err @@ -19,10 +20,9 @@ func NewAssignTaskCommandHandler(as hwes.AggregateStore) AssignTaskCommandHandle newToken := common.ConsistencyToken(task.GetVersion()) if expConsistency != nil && *expConsistency != newToken { - return 0, &UpdateTaskConflict{ - Consistency: newToken, - Was: oldState, - Is: task.Task, + return newToken, &common.Conflict[*models.Task]{ + Was: oldState, + Is: task.Task, }, nil } diff --git a/services/tasks-svc/internal/task/commands/v1/update_subtask.go b/services/tasks-svc/internal/task/commands/v1/update_subtask.go index 8c1035737..234b2d96a 100644 --- a/services/tasks-svc/internal/task/commands/v1/update_subtask.go +++ b/services/tasks-svc/internal/task/commands/v1/update_subtask.go @@ -7,12 +7,13 @@ import ( "github.com/google/uuid" "hwes" "tasks-svc/internal/task/aggregate" + "tasks-svc/internal/task/models" ) -type UpdateSubtaskCommandHandler func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) +type UpdateSubtaskCommandHandler func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Task], error) func NewUpdateSubtaskCommandHandler(as hwes.AggregateStore) UpdateSubtaskCommandHandler { - return func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *UpdateTaskConflict, error) { + return func(ctx context.Context, taskID, subtaskID uuid.UUID, name *string, done *bool, expConsistency *common.ConsistencyToken) (common.ConsistencyToken, *common.Conflict[*models.Task], error) { a, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err @@ -25,10 +26,9 @@ func NewUpdateSubtaskCommandHandler(as hwes.AggregateStore) UpdateSubtaskCommand newToken := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && *expConsistency != newToken { - return 0, &UpdateTaskConflict{ - Consistency: newToken, - Was: oldState, - Is: a.Task, + return newToken, &common.Conflict[*models.Task]{ + Was: oldState, + Is: a.Task, }, err } diff --git a/services/tasks-svc/internal/task/commands/v1/update_task.go b/services/tasks-svc/internal/task/commands/v1/update_task.go index 169bfb2eb..76798d65a 100644 --- a/services/tasks-svc/internal/task/commands/v1/update_task.go +++ b/services/tasks-svc/internal/task/commands/v1/update_task.go @@ -11,12 +11,6 @@ import ( "tasks-svc/internal/task/models" ) -type UpdateTaskConflict struct { - Consistency common.ConsistencyToken - Was *models.Task - Is *models.Task -} - type UpdateTaskCommandHandler func( ctx context.Context, taskID uuid.UUID, @@ -26,7 +20,7 @@ type UpdateTaskCommandHandler func( public *bool, dueAt *timestamppb.Timestamp, expConsistency *common.ConsistencyToken, -) (common.ConsistencyToken, *UpdateTaskConflict, error) +) (common.ConsistencyToken, *common.Conflict[*models.Task], error) func NewUpdateTaskCommandHandler(as hwes.AggregateStore) UpdateTaskCommandHandler { return func(ctx context.Context, @@ -37,7 +31,7 @@ func NewUpdateTaskCommandHandler(as hwes.AggregateStore) UpdateTaskCommandHandle public *bool, dueAt *timestamppb.Timestamp, expConsistency *common.ConsistencyToken, - ) (common.ConsistencyToken, *UpdateTaskConflict, error) { + ) (common.ConsistencyToken, *common.Conflict[*models.Task], error) { a, oldState, err := aggregate.LoadTaskAggregateWithSnapshotAt(ctx, as, taskID, expConsistency) if err != nil { return 0, nil, err @@ -45,10 +39,9 @@ func NewUpdateTaskCommandHandler(as hwes.AggregateStore) UpdateTaskCommandHandle newToken := common.ConsistencyToken(a.GetVersion()) if expConsistency != nil && *expConsistency != newToken { - return 0, &UpdateTaskConflict{ - Consistency: newToken, - Was: oldState, - Is: a.Task, + return newToken, &common.Conflict[*models.Task]{ + Was: oldState, + Is: a.Task, }, nil } From 952277b7b43d67b3daa54c4f052a50fff902bac0 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 14:44:26 +0200 Subject: [PATCH 35/42] no X-Org header anymore --- libs/hwtesting/grpc.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/libs/hwtesting/grpc.go b/libs/hwtesting/grpc.go index c85dab93d..0f4e5a8ed 100644 --- a/libs/hwtesting/grpc.go +++ b/libs/hwtesting/grpc.go @@ -27,13 +27,9 @@ type InsecureBearerToken struct { } func (t InsecureBearerToken) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { - m := map[string]string{ + return map[string]string{ "authorization": "Bearer " + t.bearer, - } - if t.orgMD != "" { - m["X-Organization"] = t.orgMD - } - return m, nil + }, nil } func (t InsecureBearerToken) RequireTransportSecurity() bool { return false From 0eec28078c6953c2b9a32f559d35ff130d01275d Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 14:46:31 +0200 Subject: [PATCH 36/42] more reverting --- libs/hwtesting/grpc.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/libs/hwtesting/grpc.go b/libs/hwtesting/grpc.go index 0f4e5a8ed..5f9db2388 100644 --- a/libs/hwtesting/grpc.go +++ b/libs/hwtesting/grpc.go @@ -21,16 +21,14 @@ import ( // ) // // Also see GetFakeTokenCredentials -type InsecureBearerToken struct { - bearer string - orgMD string -} +type InsecureBearerToken string func (t InsecureBearerToken) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { return map[string]string{ - "authorization": "Bearer " + t.bearer, + "authorization": "Bearer " + string(t), }, nil } + func (t InsecureBearerToken) RequireTransportSecurity() bool { return false } @@ -69,10 +67,7 @@ func GetFakeTokenCredentials(subOverride, orgOverride string) InsecureBearerToke } dist := make([]byte, base64.StdEncoding.EncodedLen(len(bytes))) base64.StdEncoding.Encode(dist, bytes) - return InsecureBearerToken{ - bearer: string(dist), - orgMD: orgOverride, - } + return InsecureBearerToken(dist) } func GetGrpcConn(subOverride, orgOverride string) *grpc.ClientConn { From 5ce66f591148e27240d8dd70e871b1e62e75e70d Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sun, 13 Oct 2024 14:52:57 +0200 Subject: [PATCH 37/42] less flaky? --- .../property-svc/stories/AttachPropertyValueConflict_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/property-svc/stories/AttachPropertyValueConflict_test.go b/services/property-svc/stories/AttachPropertyValueConflict_test.go index 4cd2971ec..ead9c7adc 100644 --- a/services/property-svc/stories/AttachPropertyValueConflict_test.go +++ b/services/property-svc/stories/AttachPropertyValueConflict_test.go @@ -121,6 +121,7 @@ func TestAttachPropertyValueConflict_Number(t *testing.T) { assert.NoError(t, err) assert.Nil(t, wasRes.Conflict) + hwtesting.WaitForProjectionsToSettle() initialConsistency := wasRes.Consistency From cbd68951c702c60d16de532303a4f580eeb79ad7 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Mon, 14 Oct 2024 13:35:09 +0200 Subject: [PATCH 38/42] deduplicate AttributeConflict --- .../util => libs/hwgrpc}/conflict.go | 6 ++-- libs/hwgrpc/go.mod | 9 +++++ libs/hwgrpc/go.sum | 6 ++++ services/property-svc/go.mod | 4 ++- .../internal/property-value/api/grpc.go | 4 +-- .../internal/property/api/grpc.go | 14 ++++---- services/tasks-svc/go.mod | 2 ++ services/tasks-svc/internal/bed/bed.go | 6 ++-- .../tasks-svc/internal/patient/api/grpc.go | 10 +++--- services/tasks-svc/internal/room/room.go | 4 +-- .../internal/task-template/task_template.go | 8 ++--- services/tasks-svc/internal/task/api/grpc.go | 14 ++++---- services/tasks-svc/internal/util/conflict.go | 35 ------------------- services/tasks-svc/internal/ward/ward.go | 4 +-- 14 files changed, 55 insertions(+), 71 deletions(-) rename {services/property-svc/util => libs/hwgrpc}/conflict.go (81%) create mode 100644 libs/hwgrpc/go.mod create mode 100644 libs/hwgrpc/go.sum delete mode 100644 services/tasks-svc/internal/util/conflict.go diff --git a/services/property-svc/util/conflict.go b/libs/hwgrpc/conflict.go similarity index 81% rename from services/property-svc/util/conflict.go rename to libs/hwgrpc/conflict.go index 8b429b766..9f1c59099 100644 --- a/services/property-svc/util/conflict.go +++ b/libs/hwgrpc/conflict.go @@ -1,14 +1,14 @@ -package util +package hwgrpc import ( "fmt" - commonpb "gen/libs/common/v1" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" ) +import commonpb "gen/libs/common/v1" + // AttributeConflict is a constructor for commonpb.AttributeConflicts -// I'd love to move this somewhere else, but I also don't want common to depend on gen (and thus hwdb, hwes, ...) func AttributeConflict(is, want proto.Message) (*commonpb.AttributeConflict, error) { var err error diff --git a/libs/hwgrpc/go.mod b/libs/hwgrpc/go.mod new file mode 100644 index 000000000..586f6f323 --- /dev/null +++ b/libs/hwgrpc/go.mod @@ -0,0 +1,9 @@ +module hwgrpc + +go 1.23 + +replace gen => ../../gen/go + +require gen v0.0.0 + +require google.golang.org/protobuf v1.32.0 // indirect diff --git a/libs/hwgrpc/go.sum b/libs/hwgrpc/go.sum new file mode 100644 index 000000000..5361bc77f --- /dev/null +++ b/libs/hwgrpc/go.sum @@ -0,0 +1,6 @@ +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/services/property-svc/go.mod b/services/property-svc/go.mod index 53911fe29..c234cdedf 100644 --- a/services/property-svc/go.mod +++ b/services/property-svc/go.mod @@ -7,6 +7,7 @@ replace ( gen => ../../gen/go hwdb => ../../libs/hwdb hwes => ../../libs/hwes + hwgrpc => ../../libs/hwgrpc hwlocale => ../../libs/hwlocale hwtesting => ../../libs/hwtesting hwutil => ../../libs/hwutil @@ -22,6 +23,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.18.1 github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.1 + github.com/jinzhu/copier v0.4.0 github.com/pashagolub/pgxmock/v4 v4.3.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.33.0 @@ -30,6 +32,7 @@ require ( google.golang.org/protobuf v1.34.2 hwdb v0.0.0 hwes v0.0.0 + hwgrpc v0.0.0 hwtesting v0.0.0 hwutil v0.0.0 telemetry v0.0.0 @@ -73,7 +76,6 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/copier v0.4.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 4c50dd23c..420d09b23 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -14,11 +14,11 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" "hwes" + "hwgrpc" "hwutil" "property-svc/internal/property-value/handlers" "property-svc/internal/property-value/models" viewModels "property-svc/internal/property-view/models" - "property-svc/util" ) type MatchersRequest interface { @@ -181,7 +181,7 @@ func (s *PropertyValueGrpcService) AttachPropertyValue(ctx context.Context, req want = val.ToProtoMessage() } - conflicts["value"], err = util.AttributeConflict( + conflicts["value"], err = hwgrpc.AttributeConflict( is, want, ) diff --git a/services/property-svc/internal/property/api/grpc.go b/services/property-svc/internal/property/api/grpc.go index e897e47cb..1d4262c42 100644 --- a/services/property-svc/internal/property/api/grpc.go +++ b/services/property-svc/internal/property/api/grpc.go @@ -13,10 +13,10 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/wrapperspb" "hwes" + "hwgrpc" "hwutil" "property-svc/internal/property/handlers" "property-svc/internal/property/models" - "property-svc/util" "slices" ) @@ -209,7 +209,7 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update subjTypeUpdateRequested := req.SubjectType != nil && *req.SubjectType != conflict.Is.SubjectType subjTypeAlreadyUpdated := conflict.Was.SubjectType != conflict.Is.SubjectType if subjTypeUpdateRequested && subjTypeAlreadyUpdated { - conflicts["subject_type"], err = util.AttributeConflict( + conflicts["subject_type"], err = hwgrpc.AttributeConflict( wrapperspb.Int32(int32(conflict.Is.SubjectType.Number())), wrapperspb.Int32(int32(req.SubjectType.Number())), ) @@ -221,7 +221,7 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update nameUpdateRequested := req.Name != nil && *req.Name != conflict.Is.Name nameAlreadyUpdated := conflict.Was.Name != conflict.Is.Name if nameUpdateRequested && nameAlreadyUpdated { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.Name), wrapperspb.String(*req.Name), ) @@ -232,7 +232,7 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update descrUpdateRequested := req.Description != nil && *req.Description != conflict.Is.Description descrAlreadyUpdated := conflict.Was.Description != conflict.Is.Description if descrUpdateRequested && descrAlreadyUpdated { - conflicts["description"], err = util.AttributeConflict( + conflicts["description"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.Description), wrapperspb.String(*req.Description), ) @@ -248,7 +248,7 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update is = wrapperspb.String(conflict.Is.SetID.UUID.String()) } - conflicts["set_id"], err = util.AttributeConflict( + conflicts["set_id"], err = hwgrpc.AttributeConflict( is, wrapperspb.String(*req.SetId), ) @@ -304,7 +304,7 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update optNameUpdateRequested := upsertedOption.Name != nil && *upsertedOption.Name != isOpt.Name optnameAlreadyUpdated := wasOpt.Name != isOpt.Name if optNameUpdateRequested && optnameAlreadyUpdated { - conflicts["select_data.upsert_options."+upsertedOption.Id+".name"], err = util.AttributeConflict( + conflicts["select_data.upsert_options."+upsertedOption.Id+".name"], err = hwgrpc.AttributeConflict( wrapperspb.String(isOpt.Name), wrapperspb.String(*upsertedOption.Name), ) @@ -323,7 +323,7 @@ func (s *PropertyGrpcService) UpdateProperty(ctx context.Context, req *pb.Update } if optDescUpdateRequested && optDescAlreadyUpdated() { - conflicts["select_data.upsert_options."+upsertedOption.Id+".description"], err = util.AttributeConflict( + conflicts["select_data.upsert_options."+upsertedOption.Id+".description"], err = hwgrpc.AttributeConflict( wrapperspb.String(*isOpt.Description), wrapperspb.String(*upsertedOption.Description), ) diff --git a/services/tasks-svc/go.mod b/services/tasks-svc/go.mod index 83c3cb568..ce5717039 100644 --- a/services/tasks-svc/go.mod +++ b/services/tasks-svc/go.mod @@ -7,6 +7,7 @@ replace ( decaying_lru => ../../libs/decaying_lru gen => ../../gen/go hwauthz => ../../libs/hwauthz + hwgrpc => ../../libs/hwgrpc hwdb => ../../libs/hwdb hwes => ../../libs/hwes hwlocale => ../../libs/hwlocale @@ -38,6 +39,7 @@ require ( hwlocale v0.0.0 hwtesting v0.0.0 hwutil v0.0.0 + hwgrpc v0.0.0 ) require ( diff --git a/services/tasks-svc/internal/bed/bed.go b/services/tasks-svc/internal/bed/bed.go index 671a2f094..603af7273 100644 --- a/services/tasks-svc/internal/bed/bed.go +++ b/services/tasks-svc/internal/bed/bed.go @@ -8,9 +8,9 @@ import ( "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" + "hwgrpc" "hwlocale" "hwutil" - "tasks-svc/internal/util" "tasks-svc/locale" "tasks-svc/repos/bed_repo" @@ -237,7 +237,7 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(result.OldName), wrapperspb.String(*req.Name), ) @@ -247,7 +247,7 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (* } if req.RoomId != nil && *req.RoomId != result.OldRoomID.String() { - conflicts["room_id"], err = util.AttributeConflict( + conflicts["room_id"], err = hwgrpc.AttributeConflict( wrapperspb.String(result.OldRoomID.String()), wrapperspb.String(*req.RoomId), ) diff --git a/services/tasks-svc/internal/patient/api/grpc.go b/services/tasks-svc/internal/patient/api/grpc.go index 658079cc4..7061a9bd0 100644 --- a/services/tasks-svc/internal/patient/api/grpc.go +++ b/services/tasks-svc/internal/patient/api/grpc.go @@ -15,11 +15,11 @@ import ( "hwdb" "hwdb/locale" "hwes" + "hwgrpc" "hwutil" "tasks-svc/internal/patient/handlers" "tasks-svc/internal/patient/models" "tasks-svc/internal/tracking" - "tasks-svc/internal/util" "tasks-svc/repos/bed_repo" ) @@ -417,7 +417,7 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa hriUpdateRequested := req.HumanReadableIdentifier != nil && *req.HumanReadableIdentifier != conflict.Is.HumanReadableIdentifier hriAlreadyUpdated := conflict.Was.HumanReadableIdentifier != conflict.Is.HumanReadableIdentifier if hriUpdateRequested && hriAlreadyUpdated { - conflicts["human_readable_identifier"], err = util.AttributeConflict( + conflicts["human_readable_identifier"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.HumanReadableIdentifier), wrapperspb.String(*req.HumanReadableIdentifier), ) @@ -429,7 +429,7 @@ func (s *PatientGrpcService) UpdatePatient(ctx context.Context, req *pb.UpdatePa notesUpdateRequested := req.Notes != nil && *req.Notes != conflict.Is.Notes notesAlreadyUpdated := conflict.Was.Notes != conflict.Is.Notes if notesUpdateRequested && notesAlreadyUpdated { - conflicts["notes"], err = util.AttributeConflict( + conflicts["notes"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.Notes), wrapperspb.String(*req.Notes), ) @@ -504,7 +504,7 @@ func (s *PatientGrpcService) AssignBed(ctx context.Context, req *pb.AssignBedReq if conflict.Is.BedID.Valid { is = wrapperspb.String(conflict.Is.BedID.UUID.String()) } - conflicts["bed_id"], err = util.AttributeConflict( + conflicts["bed_id"], err = hwgrpc.AttributeConflict( is, wrapperspb.String(req.BedId), ) @@ -569,7 +569,7 @@ func (s *PatientGrpcService) UnassignBed(ctx context.Context, req *pb.UnassignBe // TODO: find a generic approach if conflict.Is.BedID.Valid && conflict.Was.BedID != conflict.Is.BedID { - conflicts["bed_id"], err = util.AttributeConflict( + conflicts["bed_id"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.BedID.UUID.String()), nil, ) diff --git a/services/tasks-svc/internal/room/room.go b/services/tasks-svc/internal/room/room.go index acff97bd4..877397e9f 100644 --- a/services/tasks-svc/internal/room/room.go +++ b/services/tasks-svc/internal/room/room.go @@ -9,9 +9,9 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" + "hwgrpc" "hwutil" "tasks-svc/internal/tracking" - "tasks-svc/internal/util" "tasks-svc/repos/room_repo" pb "gen/services/tasks_svc/v1" @@ -138,7 +138,7 @@ func (ServiceServer) UpdateRoom(ctx context.Context, req *pb.UpdateRoomRequest) conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(result.OldName), wrapperspb.String(*req.Name), ) diff --git a/services/tasks-svc/internal/task-template/task_template.go b/services/tasks-svc/internal/task-template/task_template.go index ed595e99a..938dd549a 100644 --- a/services/tasks-svc/internal/task-template/task_template.go +++ b/services/tasks-svc/internal/task-template/task_template.go @@ -9,8 +9,8 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" + "hwgrpc" "hwutil" - "tasks-svc/internal/util" "tasks-svc/repos/task_template_repo" pb "gen/services/tasks_svc/v1" @@ -207,7 +207,7 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(result.OldName), wrapperspb.String(*req.Name), ) @@ -216,7 +216,7 @@ func (ServiceServer) UpdateTaskTemplate(ctx context.Context, req *pb.UpdateTaskT } } if req.Description != nil && *req.Description != result.OldDescription { - conflicts["description"], err = util.AttributeConflict( + conflicts["description"], err = hwgrpc.AttributeConflict( wrapperspb.String(result.OldDescription), wrapperspb.String(*req.Description), ) @@ -298,7 +298,7 @@ func (ServiceServer) UpdateTaskTemplateSubTask(ctx context.Context, req *pb.Upda conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != subTaskResult.OldName { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(subTaskResult.OldName), wrapperspb.String(*req.Name), ) diff --git a/services/tasks-svc/internal/task/api/grpc.go b/services/tasks-svc/internal/task/api/grpc.go index 2dc66028f..d431a2c64 100644 --- a/services/tasks-svc/internal/task/api/grpc.go +++ b/services/tasks-svc/internal/task/api/grpc.go @@ -14,10 +14,10 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" "hwes" + "hwgrpc" "hwutil" "tasks-svc/internal/task/handlers" "tasks-svc/internal/task/models" - "tasks-svc/internal/util" "time" ) @@ -113,7 +113,7 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ nameUpdateRequested := req.Name != nil && *req.Name != conflict.Is.Name nameAlreadyUpdated := conflict.Was.Name != conflict.Is.Name if nameUpdateRequested && nameAlreadyUpdated { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.Name), wrapperspb.String(*req.Name), ) @@ -125,7 +125,7 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ descrUpdateRequested := req.Description != nil && *req.Description != conflict.Is.Description descrAlreadyUpdated := conflict.Was.Description != conflict.Is.Description if descrUpdateRequested && descrAlreadyUpdated { - conflicts["description"], err = util.AttributeConflict( + conflicts["description"], err = hwgrpc.AttributeConflict( wrapperspb.String(conflict.Is.Description), wrapperspb.String(*req.Description), ) @@ -142,7 +142,7 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ if conflict.Is.DueAt != nil { is = timestamppb.New(*conflict.Is.DueAt) } - conflicts["due_at"], err = util.AttributeConflict( + conflicts["due_at"], err = hwgrpc.AttributeConflict( is, req.DueAt, ) @@ -154,7 +154,7 @@ func (s *TaskGrpcService) UpdateTask(ctx context.Context, req *pb.UpdateTaskRequ statusUpdateRequested := req.Status != nil && *req.Status != conflict.Is.Status statusAlreadyUpdated := conflict.Was.Status != conflict.Is.Status if statusUpdateRequested && statusAlreadyUpdated { - conflicts["status"], err = util.AttributeConflict( + conflicts["status"], err = hwgrpc.AttributeConflict( wrapperspb.Int32(int32(conflict.Is.Status)), wrapperspb.Int32(int32(*req.Status)), ) @@ -225,7 +225,7 @@ func (s *TaskGrpcService) AssignTask(ctx context.Context, req *pb.AssignTaskRequ if conflict.Is.AssignedUser.Valid { is = wrapperspb.String(conflict.Is.AssignedUser.UUID.String()) } - conflicts["user_id"], err = util.AttributeConflict( + conflicts["user_id"], err = hwgrpc.AttributeConflict( is, wrapperspb.String(req.UserId), ) @@ -563,7 +563,7 @@ func (s *TaskGrpcService) UpdateSubtask(ctx context.Context, req *pb.UpdateSubta nameUpdateRequested := req.Subtask.Name != nil && *req.Subtask.Name != is.Name nameAlreadyUpdated := was.Name != is.Name if nameUpdateRequested && nameAlreadyUpdated { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(is.Name), wrapperspb.String(*req.Subtask.Name), ) diff --git a/services/tasks-svc/internal/util/conflict.go b/services/tasks-svc/internal/util/conflict.go deleted file mode 100644 index 8b429b766..000000000 --- a/services/tasks-svc/internal/util/conflict.go +++ /dev/null @@ -1,35 +0,0 @@ -package util - -import ( - "fmt" - commonpb "gen/libs/common/v1" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" -) - -// AttributeConflict is a constructor for commonpb.AttributeConflicts -// I'd love to move this somewhere else, but I also don't want common to depend on gen (and thus hwdb, hwes, ...) -func AttributeConflict(is, want proto.Message) (*commonpb.AttributeConflict, error) { - var err error - - var wantAny *anypb.Any - if want != nil { - wantAny, err = anypb.New(want) - if err != nil { - return nil, fmt.Errorf("AttributeConflict could not marshal want: %w", err) - } - } - - var isAny *anypb.Any - if is != nil { - isAny, err = anypb.New(is) - if err != nil { - return nil, fmt.Errorf("AttributeConflict could not marshal is: %w", err) - } - } - - return &commonpb.AttributeConflict{ - Is: isAny, - Want: wantAny, - }, nil -} diff --git a/services/tasks-svc/internal/ward/ward.go b/services/tasks-svc/internal/ward/ward.go index 3a575c6be..dd414898b 100644 --- a/services/tasks-svc/internal/ward/ward.go +++ b/services/tasks-svc/internal/ward/ward.go @@ -11,9 +11,9 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/wrapperspb" "hwdb" + "hwgrpc" "hwutil" "tasks-svc/internal/tracking" - "tasks-svc/internal/util" "tasks-svc/repos/ward_repo" ) @@ -178,7 +178,7 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest) conflicts := make(map[string]*commonpb.AttributeConflict) if req.Name != nil && *req.Name != result.OldName { - conflicts["name"], err = util.AttributeConflict( + conflicts["name"], err = hwgrpc.AttributeConflict( wrapperspb.String(result.OldName), wrapperspb.String(*req.Name), ) From 297b26055efe3bf266c36a581f04a7156cacf9c3 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Mon, 14 Oct 2024 14:35:34 +0200 Subject: [PATCH 39/42] use an enum (like) structure vor TypedValue --- .../internal/property-value/api/grpc.go | 72 ++++++++++--------- .../property-value/models/propertyValue.go | 41 ++++++++--- .../v1/get_property_values_by_subject_id.go | 41 ++++++----- 3 files changed, 92 insertions(+), 62 deletions(-) diff --git a/services/property-svc/internal/property-value/api/grpc.go b/services/property-svc/internal/property-value/api/grpc.go index 420d09b23..7062a9e9f 100644 --- a/services/property-svc/internal/property-value/api/grpc.go +++ b/services/property-svc/internal/property-value/api/grpc.go @@ -19,6 +19,7 @@ import ( "property-svc/internal/property-value/handlers" "property-svc/internal/property-value/models" viewModels "property-svc/internal/property-view/models" + "time" ) type MatchersRequest interface { @@ -239,42 +240,49 @@ func (s *PropertyValueGrpcService) GetAttachedPropertyValues(ctx context.Context PropertyConsistency: pnv.PropertyConsistency, ValueConsistency: pnv.ValueConsistency, } - switch { - case pnv.Value == nil: + + if pnv.Value == nil { return res - case pnv.Value.TextValue != nil: - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_TextValue{TextValue: *pnv.Value.TextValue} - case pnv.Value.BoolValue != nil: - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_BoolValue{BoolValue: *pnv.Value.BoolValue} - case pnv.Value.NumberValue != nil: - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_NumberValue{NumberValue: *pnv.Value.NumberValue} - case len(pnv.Value.MultiSelectValues) != 0 && pnv.FieldType == pb.FieldType_FIELD_TYPE_SELECT: - v := pnv.Value.MultiSelectValues[0] - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_SelectValue{ - SelectValue: &pb.SelectValueOption{ - Id: v.Id.String(), - Name: v.Name, - Description: v.Description, - }, - } - case len(pnv.Value.MultiSelectValues) != 0 && pnv.FieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT: - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_MultiSelectValue{ - MultiSelectValue: &pb.MultiSelectValue{ - SelectValues: hwutil.Map(pnv.Value.MultiSelectValues, func(o models.SelectValueOption) *pb.SelectValueOption { - return &pb.SelectValueOption{ - Id: o.Id.String(), - Name: o.Name, - Description: o.Description, - } - }), - }, + } + + switch val := pnv.Value.(type) { + + case models.TextValue: + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_TextValue{TextValue: string(val)} + case models.BoolValue: + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_BoolValue{BoolValue: bool(val)} + case models.NumberValue: + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_NumberValue{NumberValue: float64(val)} + case models.MultiSelectValues: + // TODO: use value encoding + if pnv.FieldType == pb.FieldType_FIELD_TYPE_SELECT { + v := val[0] + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_SelectValue{ + SelectValue: &pb.SelectValueOption{ + Id: v.Id.String(), + Name: v.Name, + Description: v.Description, + }, + } + } else if pnv.FieldType == pb.FieldType_FIELD_TYPE_MULTI_SELECT { + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_MultiSelectValue{ + MultiSelectValue: &pb.MultiSelectValue{ + SelectValues: hwutil.Map(val, func(o models.SelectValueOption) *pb.SelectValueOption { + return &pb.SelectValueOption{ + Id: o.Id.String(), + Name: o.Name, + Description: o.Description, + } + }), + }, + } } - case pnv.Value.DateTimeValue != nil: - res.Value = &pb.GetAttachedPropertyValuesResponse_Value_DateTimeValue{DateTimeValue: timestamppb.New(*pnv.Value.DateTimeValue)} - case pnv.Value.DateValue != nil: + case models.DateTimeValue: + res.Value = &pb.GetAttachedPropertyValuesResponse_Value_DateTimeValue{DateTimeValue: timestamppb.New(time.Time(val))} + case models.DateValue: res.Value = &pb.GetAttachedPropertyValuesResponse_Value_DateValue{ DateValue: &pb.Date{ - Date: timestamppb.New(*pnv.Value.DateValue), + Date: timestamppb.New(time.Time(val)), }, } default: diff --git a/services/property-svc/internal/property-value/models/propertyValue.go b/services/property-svc/internal/property-value/models/propertyValue.go index 21cf42bb8..03c3ebc65 100644 --- a/services/property-svc/internal/property-value/models/propertyValue.go +++ b/services/property-svc/internal/property-value/models/propertyValue.go @@ -101,16 +101,39 @@ func (v SimpleTypedValue) ToProtoMessage() proto.Message { } } -type TypedValue struct { - TextValue *string - BoolValue *bool - NumberValue *float64 - DateTimeValue *time.Time - DateValue *time.Time - SingleSelectValue *SelectValueOption - MultiSelectValues []SelectValueOption +// TypedValue is a poor man's enum +type TypedValue interface { + isTypedValue() } +type TextValue string + +func (v TextValue) isTypedValue() {} + +type BoolValue bool + +func (v BoolValue) isTypedValue() {} + +type NumberValue float64 + +func (v NumberValue) isTypedValue() {} + +type DateTimeValue time.Time + +func (v DateTimeValue) isTypedValue() {} + +type DateValue time.Time + +func (v DateValue) isTypedValue() {} + +type SingleSelectValue SelectValueOption + +func (v SingleSelectValue) isTypedValue() {} + +type MultiSelectValues []SelectValueOption + +func (v MultiSelectValues) isTypedValue() {} + type MultiSelectChange struct { SelectValues []string `json:"select_values,omitempty"` RemoveSelectValues []string `json:"remove_select_values,omitempty"` @@ -130,7 +153,7 @@ type PropertyAndValue struct { PropertyConsistency string ValueConsistency *string - Value *TypedValue + Value TypedValue } type TypedValueChange struct { diff --git a/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go b/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go index 0c1fe78a2..66f2f9205 100644 --- a/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go +++ b/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go @@ -6,7 +6,7 @@ import ( "fmt" pb "gen/services/property_svc/v1" "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" + "github.com/rs/zerolog/log" "hwdb" "hwes" "hwutil" @@ -14,7 +14,6 @@ import ( vh "property-svc/internal/property-view/handlers" viewModels "property-svc/internal/property-view/models" "property-svc/repos/property_value_repo" - "time" ) type GetRelevantPropertyValuesQueryHandler func(ctx context.Context, matcher viewModels.PropertyMatchers) ([]models.PropertyAndValue, error) @@ -72,17 +71,12 @@ func NewGetRelevantPropertyValuesQueryHandler(as hwes.AggregateStore) GetRelevan // make sure MultiSelectValues is an array if properties[row.Property.ID].Value == nil { - properties[row.Property.ID].Value = &models.TypedValue{ - TextValue: nil, - BoolValue: nil, - NumberValue: nil, - MultiSelectValues: make([]models.SelectValueOption, 0), - DateTimeValue: nil, - } + properties[row.Property.ID].Value = models.MultiSelectValues(make([]models.SelectValueOption, 0)) } // add multiselectvalue to array - properties[row.Property.ID].Value.MultiSelectValues = append(properties[row.Property.ID].Value.MultiSelectValues, models.SelectValueOption{ + arr := properties[row.Property.ID].Value.(models.MultiSelectValues) + properties[row.Property.ID].Value = append(arr, models.SelectValueOption{ Id: row.SelectOptionID.UUID, // known to be valid by if Name: *row.SelectOptionName, // known to be set due to NOT NULL and successful LEFT JOIN Description: *row.SelectOptionDescription, // known to be set due to NOT NULL and successful LEFT JOIN @@ -91,17 +85,22 @@ func NewGetRelevantPropertyValuesQueryHandler(as hwes.AggregateStore) GetRelevan // basic values can just be set, we expect only one of them to be not null, // but at least one has to due to ifs - properties[row.Property.ID].Value = &models.TypedValue{ - TextValue: row.TextValue, - BoolValue: row.BoolValue, - NumberValue: row.NumberValue, - MultiSelectValues: nil, - DateTimeValue: hwutil.MapIf(row.DateTimeValue.Valid, row.DateTimeValue, func(dtV pgtype.Timestamp) time.Time { - return dtV.Time - }), - DateValue: hwutil.MapIf(row.DateValue.Valid, row.DateValue, func(dV pgtype.Date) time.Time { - return dV.Time - }), + + switch { + case row.TextValue != nil: + properties[row.Property.ID].Value = models.TextValue(*row.TextValue) + case row.BoolValue != nil: + properties[row.Property.ID].Value = models.BoolValue(*row.BoolValue) + case row.NumberValue != nil: + properties[row.Property.ID].Value = models.NumberValue(*row.NumberValue) + case row.DateValue.Valid: + properties[row.Property.ID].Value = models.DateValue(row.DateValue.Time) + case row.DateTimeValue.Valid: + properties[row.Property.ID].Value = models.DateTimeValue(row.DateTimeValue.Time) + default: + log.Warn().Interface("row", row).Msg("row is all nil, one case does not seem to be handled") + // if you are debugging the above log line, consider updating the "if" in the beginning + continue } } } From b96d194f607cdaf974b82c133ea6663618a5ed1c Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sat, 26 Oct 2024 11:43:32 +0200 Subject: [PATCH 40/42] fix linting issues, prepare test fix (blocked) --- .golangci.yaml | 1 + libs/hwes/eventstoredb/integration_test.go | 2 +- .../queries/v1/get_property_values_by_subject_id.go | 5 +++-- .../stories/GetPropertieBySubjectType_test.go | 8 ++++++++ services/tasks-svc/stories/BedCRUD_test.go | 4 ++-- services/tasks-svc/stories/RoomCRUD_test.go | 2 +- services/tasks-svc/stories/TaskTemplateCRUD_test.go | 6 +++--- services/tasks-svc/stories/WardCRUD_test.go | 2 +- 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index c2a6a778e..c77faca81 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -43,3 +43,4 @@ linters: - stylecheck # TODO - tagliatelle # TODO - testpackage # TODO + - maintidx # TODO diff --git a/libs/hwes/eventstoredb/integration_test.go b/libs/hwes/eventstoredb/integration_test.go index ce7ee4830..4820c2804 100644 --- a/libs/hwes/eventstoredb/integration_test.go +++ b/libs/hwes/eventstoredb/integration_test.go @@ -46,7 +46,7 @@ func TestLoadN(t *testing.T) { sendAggregate := hwes.NewAggregateBase(hwes.AggregateType(eventType), id) sendAggregate.RegisterEventListener(eventType, func(evt hwes.Event) error { return nil }) - for i := 0; i < 4; i++ { + for range 4 { event, _ := hwes.NewEvent(sendAggregate, eventType, hwes.WithContext(ctx)) require.NoError(t, sendAggregate.Apply(event)) } diff --git a/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go b/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go index 967ebce00..6a73eafff 100644 --- a/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go +++ b/services/property-svc/internal/property-value/queries/v1/get_property_values_by_subject_id.go @@ -90,12 +90,13 @@ func NewGetRelevantPropertyValuesQueryHandler(as hwes.AggregateStore) GetRelevan } // add multiselectvalue to array - arr := properties[row.Property.ID].Value.(models.MultiSelectValues) - properties[row.Property.ID].Value = append(arr, models.SelectValueOption{ + arr := properties[row.Property.ID].Value.(models.MultiSelectValues) //nolint:forcetypeassert + arr = append(arr, models.SelectValueOption{ Id: row.SelectOptionID.UUID, // known to be valid by if Name: *row.SelectOptionName, // known to be set due to NOT NULL and successful LEFT JOIN Description: *row.SelectOptionDescription, // known to be set due to NOT NULL and successful LEFT JOIN }) + properties[row.Property.ID].Value = arr } else { // basic values can just be set, we expect only one of them to be not null, // but at least one has to due to ifs diff --git a/services/property-svc/stories/GetPropertieBySubjectType_test.go b/services/property-svc/stories/GetPropertieBySubjectType_test.go index 7dfe97f43..198152bc8 100644 --- a/services/property-svc/stories/GetPropertieBySubjectType_test.go +++ b/services/property-svc/stories/GetPropertieBySubjectType_test.go @@ -19,6 +19,14 @@ import ( // - Check GetPropertiesBySubjectType func TestGetProperties(t *testing.T) { orgID := uuid.New().String() + /* TODO: blocked by #880 + authz := spicedb.NewSpiceDBAuthZ() + authz.Create(hwauthz.NewRelationship( + commonPerm.User(uuid.MustParse(hwtesting.FakeTokenUser)), + "member", + commonPerm.Organization(orgID), + )) + */ propertyClient := pb.NewPropertyServiceClient(hwtesting.GetGrpcConn("", orgID)) ctx := context.Background() diff --git a/services/tasks-svc/stories/BedCRUD_test.go b/services/tasks-svc/stories/BedCRUD_test.go index c26d85c69..7c83ab0ea 100644 --- a/services/tasks-svc/stories/BedCRUD_test.go +++ b/services/tasks-svc/stories/BedCRUD_test.go @@ -227,7 +227,7 @@ func TestUpdateBedConflict(t *testing.T) { nameWant := &wrapperspb.StringValue{} require.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) - assert.Equal(t, name2, nameWant.Value) + assert.Equal(t, name2, nameWant.Value) //nolint:testifylint // false positive roomRes := update2Res.Conflict.ConflictingAttributes["room_id"] assert.NotNil(t, roomRes) @@ -238,7 +238,7 @@ func TestUpdateBedConflict(t *testing.T) { roomWant := &wrapperspb.StringValue{} require.NoError(t, roomRes.Want.UnmarshalTo(roomWant)) - assert.Equal(t, roomId2, roomWant.Value) + assert.Equal(t, roomId2, roomWant.Value) //nolint:testifylint // false positive // racing update 3 update3Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{ diff --git a/services/tasks-svc/stories/RoomCRUD_test.go b/services/tasks-svc/stories/RoomCRUD_test.go index 67840d0e0..66d5782d5 100644 --- a/services/tasks-svc/stories/RoomCRUD_test.go +++ b/services/tasks-svc/stories/RoomCRUD_test.go @@ -332,5 +332,5 @@ func TestUpdateRoomConflict(t *testing.T) { nameWant := &wrapperspb.StringValue{} require.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) - assert.Equal(t, name2, nameWant.Value) + assert.Equal(t, name2, nameWant.Value) //nolint:testifylint // false positive } diff --git a/services/tasks-svc/stories/TaskTemplateCRUD_test.go b/services/tasks-svc/stories/TaskTemplateCRUD_test.go index 46dd95e9b..90b1a446f 100644 --- a/services/tasks-svc/stories/TaskTemplateCRUD_test.go +++ b/services/tasks-svc/stories/TaskTemplateCRUD_test.go @@ -214,7 +214,7 @@ func TestUpdateTaskTemplateConflict(t *testing.T) { nameWant := &wrapperspb.StringValue{} require.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) - assert.Equal(t, name2, nameWant.Value) + assert.Equal(t, name2, nameWant.Value) //nolint:testifylint // false positive descrRes := update2Res.Conflict.ConflictingAttributes["description"] assert.NotNil(t, descrRes) @@ -225,7 +225,7 @@ func TestUpdateTaskTemplateConflict(t *testing.T) { descrWant := &wrapperspb.StringValue{} require.NoError(t, descrRes.Want.UnmarshalTo(descrWant)) - assert.Equal(t, name2, descrWant.Value) + assert.Equal(t, name2, descrWant.Value) //nolint:testifylint // false positive } func TestUpdateTaskTemplateSubTaskConflict(t *testing.T) { @@ -282,5 +282,5 @@ func TestUpdateTaskTemplateSubTaskConflict(t *testing.T) { nameWant := &wrapperspb.StringValue{} require.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) - assert.Equal(t, name2, nameWant.Value) + assert.Equal(t, name2, nameWant.Value) //nolint:testifylint // false positive } diff --git a/services/tasks-svc/stories/WardCRUD_test.go b/services/tasks-svc/stories/WardCRUD_test.go index 8433d7fbd..a13d288b9 100644 --- a/services/tasks-svc/stories/WardCRUD_test.go +++ b/services/tasks-svc/stories/WardCRUD_test.go @@ -432,5 +432,5 @@ func TestUpdateWardConflict(t *testing.T) { nameWant := &wrapperspb.StringValue{} require.NoError(t, nameRes.Want.UnmarshalTo(nameWant)) - assert.Equal(t, name2, nameWant.Value) + assert.Equal(t, name2, nameWant.Value) //nolint:testifylint // false positive } From 2685f025f1957c974ac414439a4bca11babafdb3 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sat, 26 Oct 2024 11:47:08 +0200 Subject: [PATCH 41/42] unblock by cherrypick --- libs/hwauthz/commonPerm/userOrg.go | 35 +++++++++++++++++++ .../stories/GetPropertieBySubjectType_test.go | 18 +++++----- 2 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 libs/hwauthz/commonPerm/userOrg.go diff --git a/libs/hwauthz/commonPerm/userOrg.go b/libs/hwauthz/commonPerm/userOrg.go new file mode 100644 index 000000000..07b913e83 --- /dev/null +++ b/libs/hwauthz/commonPerm/userOrg.go @@ -0,0 +1,35 @@ +package commonPerm + +import ( + "common/auth" + "context" + + "github.com/google/uuid" +) + +type User uuid.UUID + +func (t User) Type() string { return "user" } +func (t User) ID() string { return uuid.UUID(t).String() } + +func UserFromCtx(ctx context.Context) (User, error) { + userID, err := auth.GetUserID(ctx) + if err != nil { + return User{}, err + } + return User(userID), nil +} + +type Organization uuid.UUID + +func (p Organization) Type() string { return "organization" } +func (p Organization) ID() string { return uuid.UUID(p).String() } + +func OrganizationFromCtx(ctx context.Context) (Organization, error) { + organizationID, err := auth.GetOrganizationID(ctx) + if err != nil { + return Organization{}, err + } + return Organization(organizationID), nil +} + diff --git a/services/property-svc/stories/GetPropertieBySubjectType_test.go b/services/property-svc/stories/GetPropertieBySubjectType_test.go index 198152bc8..1b4f4b27c 100644 --- a/services/property-svc/stories/GetPropertieBySubjectType_test.go +++ b/services/property-svc/stories/GetPropertieBySubjectType_test.go @@ -4,6 +4,9 @@ import ( "context" "encoding/json" pb "gen/services/property_svc/v1" + "hwauthz" + "hwauthz/commonPerm" + "hwauthz/spicedb" "hwtesting" "hwutil" "regexp" @@ -18,18 +21,17 @@ import ( // - Create Properties // - Check GetPropertiesBySubjectType func TestGetProperties(t *testing.T) { - orgID := uuid.New().String() - /* TODO: blocked by #880 + ctx := context.Background() + + orgID := uuid.New() authz := spicedb.NewSpiceDBAuthZ() - authz.Create(hwauthz.NewRelationship( + _, err := authz.Create(hwauthz.NewRelationship( commonPerm.User(uuid.MustParse(hwtesting.FakeTokenUser)), "member", commonPerm.Organization(orgID), - )) - */ - propertyClient := pb.NewPropertyServiceClient(hwtesting.GetGrpcConn("", orgID)) - - ctx := context.Background() + )).Commit(ctx) + require.NoError(t, err) + propertyClient := pb.NewPropertyServiceClient(hwtesting.GetGrpcConn("", orgID.String())) // // Create new Properties From 0203aaeb0525cff73e7812e1987f9711de04ff8e Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Sat, 26 Oct 2024 17:25:27 +0200 Subject: [PATCH 42/42] lint --- libs/hwauthz/commonPerm/userOrg.go | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/hwauthz/commonPerm/userOrg.go b/libs/hwauthz/commonPerm/userOrg.go index 07b913e83..3973e2985 100644 --- a/libs/hwauthz/commonPerm/userOrg.go +++ b/libs/hwauthz/commonPerm/userOrg.go @@ -32,4 +32,3 @@ func OrganizationFromCtx(ctx context.Context) (Organization, error) { } return Organization(organizationID), nil } -