diff --git a/backend/v3/api/org/v2/convert/README.md b/backend/v3/api/org/v2/convert/README.md new file mode 100644 index 00000000000..43ac85dfbf2 --- /dev/null +++ b/backend/v3/api/org/v2/convert/README.md @@ -0,0 +1,5 @@ +An objective of Zitadel v5 is to remove the v2beta APIs. + +Once this goal is achieved, this package will be useless, as well as the Beta endpoints in `org.go`. + +Please do the cleanup. \ No newline at end of file diff --git a/backend/v3/api/org/v2/convert/object.go b/backend/v3/api/org/v2/convert/object.go new file mode 100644 index 00000000000..e1a401cbdbd --- /dev/null +++ b/backend/v3/api/org/v2/convert/object.go @@ -0,0 +1,29 @@ +package convert + +import ( + v2_object "github.com/zitadel/zitadel/pkg/grpc/object/v2" + v2beta_object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" +) + +func TextQueryMethodBetaToV2(txtMethod v2beta_object.TextQueryMethod) v2_object.TextQueryMethod { + switch txtMethod { + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE + case v2beta_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: + fallthrough + default: + return v2_object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS + } +} diff --git a/backend/v3/api/org/v2/convert/org.go b/backend/v3/api/org/v2/convert/org.go new file mode 100644 index 00000000000..26b2bd0ee38 --- /dev/null +++ b/backend/v3/api/org/v2/convert/org.go @@ -0,0 +1,90 @@ +package convert + +import ( + "github.com/zitadel/zitadel/pkg/grpc/object/v2" + v2_org "github.com/zitadel/zitadel/pkg/grpc/org/v2" + v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" +) + +func OrganizationBetaRequestToV2Request(in *v2beta_org.ListOrganizationsRequest) *v2_org.ListOrganizationsRequest { + return &v2_org.ListOrganizationsRequest{ + Query: &object.ListQuery{ + Offset: in.GetPagination().GetOffset(), + Limit: in.GetPagination().GetLimit(), + Asc: in.GetPagination().GetAsc(), + }, + SortingColumn: organizationSortingColumnBetaToV2(in.GetSortingColumn()), + Queries: organizationQueriesBetaToV2(in.GetFilter()), + } +} + +func organizationSortingColumnBetaToV2(sc v2beta_org.OrgFieldName) v2_org.OrganizationFieldName { + switch sc { + case v2beta_org.OrgFieldName_ORG_FIELD_NAME_NAME: + return v2_org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_NAME + case v2beta_org.OrgFieldName_ORG_FIELD_NAME_CREATION_DATE, v2beta_org.OrgFieldName_ORG_FIELD_NAME_UNSPECIFIED: + fallthrough + default: + return v2_org.OrganizationFieldName_ORGANIZATION_FIELD_NAME_UNSPECIFIED + } +} + +func organizationQueriesBetaToV2(queries []*v2beta_org.OrganizationSearchFilter) []*v2_org.SearchQuery { + toReturn := make([]*v2_org.SearchQuery, len(queries)) + + for i, query := range queries { + toReturn[i] = organizationQueryBetaToV2(query) + } + + return toReturn +} + +func organizationQueryBetaToV2(query *v2beta_org.OrganizationSearchFilter) *v2_org.SearchQuery { + toReturn := &v2_org.SearchQuery{} + + switch assertedType := query.GetFilter().(type) { + case *v2beta_org.OrganizationSearchFilter_DomainFilter: + toReturn.Query = &v2_org.SearchQuery_DomainQuery{ + DomainQuery: &v2_org.OrganizationDomainQuery{ + Domain: assertedType.DomainFilter.GetDomain(), + Method: TextQueryMethodBetaToV2(assertedType.DomainFilter.GetMethod()), + }, + } + + case *v2beta_org.OrganizationSearchFilter_IdFilter: + toReturn.Query = &v2_org.SearchQuery_IdQuery{ + IdQuery: &v2_org.OrganizationIDQuery{ + Id: assertedType.IdFilter.GetId(), + }, + } + case *v2beta_org.OrganizationSearchFilter_NameFilter: + toReturn.Query = &v2_org.SearchQuery_NameQuery{ + NameQuery: &v2_org.OrganizationNameQuery{ + Name: assertedType.NameFilter.GetName(), + Method: TextQueryMethodBetaToV2(assertedType.NameFilter.GetMethod()), + }, + } + case *v2beta_org.OrganizationSearchFilter_StateFilter: + toReturn.Query = &v2_org.SearchQuery_StateQuery{ + StateQuery: &v2_org.OrganizationStateQuery{ + State: organizationStateBetaToV2(assertedType.StateFilter.GetState()), + }, + } + default: + return toReturn + } + return toReturn +} + +func organizationStateBetaToV2(in v2beta_org.OrgState) v2_org.OrganizationState { + switch in { + case v2beta_org.OrgState_ORG_STATE_ACTIVE: + return v2_org.OrganizationState_ORGANIZATION_STATE_ACTIVE + case v2beta_org.OrgState_ORG_STATE_INACTIVE: + return v2_org.OrganizationState_ORGANIZATION_STATE_INACTIVE + case v2beta_org.OrgState_ORG_STATE_UNSPECIFIED, v2beta_org.OrgState_ORG_STATE_REMOVED: + fallthrough + default: + return v2_org.OrganizationState_ORGANIZATION_STATE_UNSPECIFIED + } +} diff --git a/backend/v3/api/org/v2/org.go b/backend/v3/api/org/v2/org.go index c725498b656..a46325671cc 100644 --- a/backend/v3/api/org/v2/org.go +++ b/backend/v3/api/org/v2/org.go @@ -6,8 +6,11 @@ import ( "connectrpc.com/connect" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/zitadel/zitadel/backend/v3/api/org/v2/convert" "github.com/zitadel/zitadel/backend/v3/domain" "github.com/zitadel/zitadel/backend/v3/storage/database/repository" + filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta" + "github.com/zitadel/zitadel/pkg/grpc/object/v2" v2_org "github.com/zitadel/zitadel/pkg/grpc/org/v2" v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" ) @@ -53,6 +56,8 @@ func UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org // TODO(IAM-Marco) Check if passing the pointer is actually working to retrieve the domain name and the DomainVerified domainRemoveCmd := domain.NewRemoveOrgDomainCommand(request.Msg.GetId(), orgUpdtCmd.OldDomainName, orgUpdtCmd.IsOldDomainVerified) + // TODO(IAM-Marco): I noticed while debugging that this is calling twice the commands (I think?) + // It's hard to debug, I haven't spent too much into it. Only drawback is pushing events twice. batchCmd := domain.BatchCommands(orgUpdtCmd, domainAddCmd, domainSetPrimaryCmd, domainRemoveCmd) err := domain.Invoke(ctx, batchCmd, domain.WithOrganizationRepo(repository.OrganizationRepository)) @@ -62,7 +67,7 @@ func UpdateOrganization(ctx context.Context, request *connect.Request[v2beta_org return &connect.Response[v2beta_org.UpdateOrganizationResponse]{ Msg: &v2beta_org.UpdateOrganizationResponse{ - // TODO(IAM-Marco) Change this with the real update date when OrganizationRepo.Update() + // TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update() // returns the timestamp ChangeDate: timestamppb.Now(), }, @@ -77,9 +82,36 @@ func ListOrganizations(ctx context.Context, request *connect.Request[v2_org.List return nil, err } + orgs := orgListCmd.ResultToGRPC() return &connect.Response[v2_org.ListOrganizationsResponse]{ Msg: &v2_org.ListOrganizationsResponse{ Result: orgListCmd.ResultToGRPC(), + Details: &object.ListDetails{ + // TODO(IAM-Marco): Return correct result once permissions are in place + TotalResult: uint64(len(orgs)), + }, + SortingColumn: request.Msg.GetSortingColumn(), + }, + }, nil +} + +// TODO(IAM-Marco): Remove in V5 +func ListOrganizationsBeta(ctx context.Context, request *connect.Request[v2beta_org.ListOrganizationsRequest]) (*connect.Response[v2beta_org.ListOrganizationsResponse], error) { + orgListCmd := domain.NewListOrgsCommand(convert.OrganizationBetaRequestToV2Request(request.Msg)) + + err := domain.Invoke(ctx, orgListCmd) + if err != nil { + return nil, err + } + + orgs := orgListCmd.ResultToGRPCBeta() + return &connect.Response[v2beta_org.ListOrganizationsResponse]{ + Msg: &v2beta_org.ListOrganizationsResponse{ + Organizations: orgs, + Pagination: &filter.PaginationResponse{ + TotalResult: uint64(len(orgs)), + AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()), + }, }, }, nil } diff --git a/backend/v3/domain/org_list.go b/backend/v3/domain/org_list.go index 797f7743604..3ebffa4b747 100644 --- a/backend/v3/domain/org_list.go +++ b/backend/v3/domain/org_list.go @@ -12,6 +12,7 @@ import ( "github.com/zitadel/zitadel/pkg/grpc/object/v2" "github.com/zitadel/zitadel/pkg/grpc/org/v2" v2_org "github.com/zitadel/zitadel/pkg/grpc/org/v2" + v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" ) type ListOrgsCommand struct { @@ -180,3 +181,27 @@ func (l *ListOrgsCommand) orgToGRPC(org *Organization) *v2_org.Organization { PrimaryDomain: org.PrimaryDomain(), } } + +// TODO(IAM-Marco): Remove in V5 +func (l *ListOrgsCommand) ResultToGRPCBeta() []*v2beta_org.Organization { + toReturn := make([]*v2beta_org.Organization, len(l.Result)) + + for i, org := range l.Result { + toReturn[i] = l.orgToGRPCBeta(org) + } + + return toReturn +} + +// TODO(IAM-Marco): Remove in V5 +func (l *ListOrgsCommand) orgToGRPCBeta(org *Organization) *v2beta_org.Organization { + return &v2beta_org.Organization{ + Id: org.ID, + ChangedDate: timestamppb.New(org.UpdatedAt), + CreationDate: timestamppb.New(org.CreatedAt), + + State: v2beta_org.OrgState(org.State), + Name: org.Name, + PrimaryDomain: org.PrimaryDomain(), + } +}