Files
zitadel/backend/v3/api/org/v2/org.go
adlerhurst b4cee886fc cleanup
2025-07-29 18:04:32 +02:00

211 lines
7.1 KiB
Go

package org
import (
"context"
"slices"
"connectrpc.com/connect"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/backend/v3/api"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/internal/api/authz"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
)
// ActivateOrganization implements [orgconnect.OrganizationServiceHandler].
func (s *Server) ActivateOrganization(ctx context.Context, req *connect.Request[org.ActivateOrganizationRequest]) (*connect.Response[org.ActivateOrganizationResponse], error) {
err := domain.Invoke(ctx, domain.NewActivateOrganizationCommand(
authz.GetInstance(ctx).InstanceID(),
req.Msg.GetId(),
))
if err != nil {
return nil, err
}
// DISCUSS(adlerhurst): does returning the ChangeDate bring any value?
return connect.NewResponse(&org.ActivateOrganizationResponse{
ChangeDate: timestamppb.Now(),
}), nil
}
// CreateOrganization implements [orgconnect.OrganizationServiceHandler].
func (s *Server) CreateOrganization(ctx context.Context, req *connect.Request[org.CreateOrganizationRequest]) (*connect.Response[org.CreateOrganizationResponse], error) {
// TODO: Implement admins
opts := make([]domain.CreateOrganizationCommandOpts, 0, 1+len(req.Msg.Admins))
if req.Msg.Id != nil {
opts = append(opts, domain.WithOrganizationID(req.Msg.GetId()))
}
cmd := domain.NewCreateOrganizationCommand(
authz.GetInstance(ctx).InstanceID(),
req.Msg.GetName(),
opts...,
)
err := domain.Invoke(ctx, cmd)
if err != nil {
return nil, err
}
return connect.NewResponse(&org.CreateOrganizationResponse{
Id: cmd.ID,
CreationDate: timestamppb.New(cmd.CreatedAt),
}), nil
}
// DeactivateOrganization implements [orgconnect.OrganizationServiceHandler].
func (s *Server) DeactivateOrganization(ctx context.Context, req *connect.Request[org.DeactivateOrganizationRequest]) (*connect.Response[org.DeactivateOrganizationResponse], error) {
err := domain.Invoke(ctx, domain.NewDeactivateOrganizationCommand(
authz.GetInstance(ctx).InstanceID(),
req.Msg.GetId(),
))
if err != nil {
return nil, err
}
// DISCUSS(adlerhurst): does returning the ChangeDate bring any value?
return connect.NewResponse(&org.DeactivateOrganizationResponse{
ChangeDate: timestamppb.Now(),
}), nil
}
// DeleteOrganization implements [orgconnect.OrganizationServiceHandler].
func (s *Server) DeleteOrganization(ctx context.Context, req *connect.Request[org.DeleteOrganizationRequest]) (*connect.Response[org.DeleteOrganizationResponse], error) {
err := domain.Invoke(ctx, domain.NewDeleteOrganizationCommand(
authz.GetInstance(ctx).InstanceID(),
req.Msg.GetId(),
))
if err != nil {
return nil, err
}
// DISCUSS(adlerhurst): does returning the DeletionDate bring any value?
return connect.NewResponse(&org.DeleteOrganizationResponse{
DeletionDate: timestamppb.Now(),
}), nil
}
// ListOrganizations implements [orgconnect.OrganizationServiceHandler].
func (s *Server) ListOrganizations(ctx context.Context, req *connect.Request[org.ListOrganizationsRequest]) (*connect.Response[org.ListOrganizationsResponse], error) {
opts := orgFiltersToDomain(req.Msg.GetFilter())
opts = slices.Grow(opts, 2)
opts = append(opts, api.V2BetaPaginationToDomain(req.Msg.GetPagination()))
if req.Msg.SortingColumn != org.OrgFieldName_ORG_FIELD_NAME_UNSPECIFIED {
opts = append(opts, domain.WithOrgQuerySortingColumn(orgFieldNameToDatabase(req.Msg.SortingColumn)))
}
query := domain.NewOrgsQuery(
authz.GetInstance(ctx).InstanceID(),
opts...,
)
err := domain.Invoke(ctx, query)
if err != nil {
return nil, err
}
return connect.NewResponse(&org.ListOrganizationsResponse{
Organizations: orgsFromDomain(query.Result),
Pagination: &filter.PaginationResponse{
AppliedLimit: uint64(req.Msg.Pagination.Limit),
// TotalResult: TODO(adlerhurst): needs implementation in lower layers
},
}), nil
}
// UpdateOrganization implements [orgconnect.OrganizationServiceHandler].
func (s *Server) UpdateOrganization(ctx context.Context, req *connect.Request[org.UpdateOrganizationRequest]) (*connect.Response[org.UpdateOrganizationResponse], error) {
opts := make([]domain.UpdateOrganizationCommandOpts, 0, 1)
if req.Msg.Name != "" {
opts = append(opts, domain.WithOrganizationName(req.Msg.GetName()))
}
err := domain.Invoke(ctx, domain.NewUpdateOrganizationCommand(
authz.GetInstance(ctx).InstanceID(),
req.Msg.GetId(),
opts...,
))
if err != nil {
return nil, err
}
return connect.NewResponse(&org.UpdateOrganizationResponse{
ChangeDate: timestamppb.Now(),
}), nil
}
func orgFiltersToDomain(filters []*org.OrganizationSearchFilter) []domain.OrgsQueryOpts {
opts := make([]domain.OrgsQueryOpts, len(filters))
for i, filter := range filters {
opts[i] = orgFilterToDomain(filter)
}
return opts
}
func orgFilterToDomain(filter *org.OrganizationSearchFilter) domain.OrgsQueryOpts {
switch f := filter.Filter.(type) {
case *org.OrganizationSearchFilter_NameFilter:
return domain.WithOrgByNameQuery(api.V2BetaTextFilterToDatabase(f.NameFilter.Method), f.NameFilter.Name)
case *org.OrganizationSearchFilter_DomainFilter:
return domain.WithOrgByDomainQuery(api.V2BetaTextFilterToDatabase(f.DomainFilter.Method), f.DomainFilter.Domain)
case *org.OrganizationSearchFilter_IdFilter:
return domain.WithOrgByIDQuery(f.IdFilter.Id)
case *org.OrganizationSearchFilter_StateFilter:
return domain.WithOrgByStateQuery(orgStateToDomain(f.StateFilter.State))
default:
panic("unknown organization search filter: " + filter.String())
}
}
func orgsFromDomain(orgs []*domain.Organization) []*org.Organization {
result := make([]*org.Organization, len(orgs))
for i, o := range orgs {
result[i] = &org.Organization{
Id: o.ID,
Name: o.Name,
State: orgStateFromDomain(o.State),
CreationDate: timestamppb.New(o.CreatedAt),
ChangedDate: timestamppb.New(o.UpdatedAt),
PrimaryDomain: orgPrimaryDomainFromDomain(o.Domains),
}
}
return result
}
func orgStateFromDomain(state domain.OrgState) org.OrgState {
switch state {
case domain.OrgStateActive:
return org.OrgState_ORG_STATE_ACTIVE
case domain.OrgStateInactive:
return org.OrgState_ORG_STATE_INACTIVE
default:
return org.OrgState_ORG_STATE_UNSPECIFIED
}
}
func orgPrimaryDomainFromDomain(domains []*domain.OrganizationDomain) string {
for _, d := range domains {
if d.IsPrimary {
return d.Domain
}
}
return ""
}
func orgFieldNameToDatabase(fieldName org.OrgFieldName) func(query *domain.OrgsQuery) database.Column {
switch fieldName {
case org.OrgFieldName_ORG_FIELD_NAME_NAME:
return domain.OrderOrgsByName
case org.OrgFieldName_ORG_FIELD_NAME_CREATION_DATE:
return domain.OrderOrgsByCreationDate
default:
return nil
}
}
func orgStateToDomain(state org.OrgState) domain.OrgState {
switch state {
case org.OrgState_ORG_STATE_ACTIVE:
return domain.OrgStateActive
case org.OrgState_ORG_STATE_INACTIVE:
return domain.OrgStateInactive
default:
// TODO: removed is not supported in the domain
panic("unknown org state: " + state.String())
}
}