Table of Contents

Class TeamServiceBase

Namespace
Tharga.Team
Assembly
Tharga.Team.dll
public abstract class TeamServiceBase
Inheritance
TeamServiceBase
Derived
Inherited Members

Constructors

TeamServiceBase(IUserService, ILogger<TeamServiceBase>, IIconStore, ITeamCache, TeamDeleteMode, InvitationOptions)

protected TeamServiceBase(IUserService userService, ILogger<TeamServiceBase> logger = null, IIconStore iconStore = null, ITeamCache cache = null, TeamDeleteMode deleteMode = TeamDeleteMode.Soft, InvitationOptions invitationOptions = null)

Parameters

userService IUserService

Resolves the calling user.

logger ILogger<TeamServiceBase>

Optional. Used to report ambiguous member matches.

iconStore IIconStore

Optional. Required only for team icons; see SetTeamIconAsync(string, byte[], string).

cache ITeamCache

Where the membership and custom-role lookups are kept. Defaults to the process-local InMemoryTeamCache, which is correct for a single instance only — forward this parameter from your own service's constructor so a shared implementation can be registered, or a multi-instance deployment will not see permission changes made through another instance. See ITeamCache.

deleteMode TeamDeleteMode
invitationOptions InvitationOptions

Properties

DeleteMode

What deleting a team does in this store. Defaults to whatever the host configured, or Soft.

protected virtual TeamDeleteMode DeleteMode { get; }

Property Value

TeamDeleteMode

Remarks

An optional constructor parameter rather than a required one, and a virtual property rather than a field read, so neither a derived host that never passes it nor one that wants to decide the mode itself has to change. Both matter in a patch.

Note this is only half the answer — see SupportsSoftDelete. A store that cannot mark a team deleted deletes outright whatever this says.

SupportsSoftDelete

Whether this store can soft-delete. false by default, so a store that has not implemented it keeps deleting exactly as before.

protected virtual bool SupportsSoftDelete { get; }

Property Value

bool

Remarks

This is what keeps soft delete a patch rather than a break. The default delete mode is Soft, but a host deriving from this class predates the feature and cannot mark a team deleted. Reporting false makes the mode resolve to Hard for that store — the behaviour it already had — instead of failing on an operation it cannot perform.

Surfaces are expected to consult this before offering restore or purge; a control that throws when clicked is worse than no control.

Methods

AddMemberAsync(string, InviteUserModel)

Invites someone, or renews the invitation they already have.

public Task AddMemberAsync(string teamKey, InviteUserModel model)

Parameters

teamKey string
model InviteUserModel

Returns

Task

Remarks

Re-inviting an address that already has an outstanding invitation keeps its code. It used to add a second member row carrying a second live code for one person, which is two working links to the same seat and one of them impossible to withdraw. Renewing instead means someone who has already mailed a link can give it more time without the recipient's link dying — the requirement behind Tharga/Team#249.

Changed details are still applied. An invitation renewed with a different access level or name takes them, because an administrator who re-invites at a different level plainly means the new one; silently keeping the old level would be the more surprising reading of the same click.

The expiry is only moved when a Lifetime is configured. Without one invitations do not expire, so there is nothing to extend and a store that has not implemented the expiry seam is not asked to.

AddTeamMemberAsync(string, InviteUserModel)

protected abstract Task AddTeamMemberAsync(string teamKey, InviteUserModel model)

Parameters

teamKey string
model InviteUserModel

Returns

Task

ClearTeamIconAsync(string)

Clears the team's icon and deletes the stored bytes. Gated by team:manage.

public Task ClearTeamIconAsync(string teamKey)

Parameters

teamKey string

Returns

Task

CreateTeamAsync(string)

public Task<ITeam> CreateTeamAsync(string name)

Parameters

name string

Returns

Task<ITeam>

CreateTeamAsync(string, string, IUser, string)

protected abstract Task<ITeam> CreateTeamAsync(string teamKey, string name, IUser user, string displayName = null)

Parameters

teamKey string
name string
user IUser
displayName string

Returns

Task<ITeam>

DeleteTeamAsync(string)

protected abstract Task DeleteTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task

DeleteTeamAsync<TMember>(string)

Deletes the team — soft by default, so it can be restored, and so deleting needs no privilege to drop storage.

public Task DeleteTeamAsync<TMember>(string teamKey) where TMember : ITeamMember

Parameters

teamKey string

Returns

Task

Type Parameters

TMember

Remarks

Resolves to a hard delete when the host asks for one, and also when the store cannot soft-delete: see SupportsSoftDelete. Either way the team disappears from every read, which is why the change of default is invisible through this API.

ExtendInvitationAsync(string, string)

Gives an outstanding invitation a fresh lifetime, keeping its code.

public Task ExtendInvitationAsync(string teamKey, string inviteKey)

Parameters

teamKey string
inviteKey string

Returns

Task

Remarks

The point is what it does not do: mint a new code. A link that has already been mailed keeps working, so extending an invitation costs the recipient nothing and needs no second message. That is only possible because the expiry lives on the invitation record rather than being derived from its creation time — see ExpiresAt.

An operation rather than a settable expiry, so it can be authorized and audited as one fact.

The new expiry is now plus the configured Lifetime. Where no lifetime is configured invitations do not expire, and extending clears any expiry the invitation was carrying.

GetAllTeamsAsync()

Every team, regardless of membership. Requires the Read system scope.

public virtual IAsyncEnumerable<ITeam> GetAllTeamsAsync()

Returns

IAsyncEnumerable<ITeam>

Remarks

Discovery only — the returned teams carry no implied access. Acting inside a team the caller is not a member of still depends on that team's consent. Use Tharga.Team.ITeamService.GetTeamsAsync() for the caller's own teams; this method is for oversight surfaces (support, administration).

GetAllTeamsAsync<TMember>()

Every team, regardless of membership. Requires the Read system scope.

public virtual IAsyncEnumerable<ITeam<TMember>> GetAllTeamsAsync<TMember>() where TMember : ITeamMember

Returns

IAsyncEnumerable<ITeam<TMember>>

Type Parameters

TMember

Remarks

Discovery only — the returned teams carry no implied access. Acting inside a team the caller is not a member of still depends on that team's consent. Use Tharga.Team.ITeamService.GetTeamsAsync() for the caller's own teams; this method is for oversight surfaces (support, administration).

GetAllTeamsInternalAsync()

Backs GetAllTeamsAsync(). Virtual rather than abstract so existing derived services keep compiling; the default returns nothing, and storage-backed bases override it.

protected virtual IAsyncEnumerable<ITeam> GetAllTeamsInternalAsync()

Returns

IAsyncEnumerable<ITeam>

GetConsentedTeamsAsync(string[])

public IAsyncEnumerable<ITeam> GetConsentedTeamsAsync(string[] userRoles)

Parameters

userRoles string[]

Returns

IAsyncEnumerable<ITeam>

GetConsentedTeamsInternalAsync(string[])

protected abstract IAsyncEnumerable<ITeam> GetConsentedTeamsInternalAsync(string[] userRoles)

Parameters

userRoles string[]

Returns

IAsyncEnumerable<ITeam>

GetInvitationInternalAsync(string, string)

protected virtual Task<Invitation> GetInvitationInternalAsync(string teamKey, string inviteKey)

Parameters

teamKey string
inviteKey string

Returns

Task<Invitation>

GetInvitedMemberNameAsync(string, string)

Look up the (admin-entered) Name of the member identified by inviteKey inside the given team. Used to capture the invitation Name before accept clears it, so it can be promoted to User.Name. Default implementation returns null; derivatives that have access to the typed team document override it.

protected virtual Task<string> GetInvitedMemberNameAsync(string teamKey, string inviteKey)

Parameters

teamKey string
inviteKey string

Returns

Task<string>

GetMembersAsync(string)

Every member on the team's roster, in any MembershipState — including invited and rejected.

public virtual IAsyncEnumerable<ITeamMember> GetMembersAsync(string teamKey)

Parameters

teamKey string

Returns

IAsyncEnumerable<ITeamMember>

Remarks

The counterpart to GetTeamMemberAsync(string, string), which returns only an active membership and cannot tell a pending invitee from a stranger. Reach for this one whenever the difference matters.

GetTeamAsync(string)

protected abstract Task<ITeam> GetTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task<ITeam>

GetTeamAsync<TMember>(string)

public Task<ITeam<TMember>> GetTeamAsync<TMember>(string teamKey) where TMember : ITeamMember

Parameters

teamKey string

Returns

Task<ITeam<TMember>>

Type Parameters

TMember

GetTeamByKeyAsync(string)

A team by key, regardless of the caller's membership — a non-generic exact read for call sites with no TMember to hand (e.g. the audit decorator capturing a "before" value for a consent change made by a non-member acting through consent). Returns null when the team does not exist.

public Task<ITeam> GetTeamByKeyAsync(string teamKey)

Parameters

teamKey string

Returns

Task<ITeam>

GetTeamCustomRolesAsync(string)

The team's runtime-defined custom roles. Requires team:read on that team.

public Task<IReadOnlyList<TenantRoleDefinition>> GetTeamCustomRolesAsync(string teamKey)

Parameters

teamKey string

Returns

Task<IReadOnlyList<TenantRoleDefinition>>

Remarks

A read of team detail, so it is gated like the others — not like its write sibling SetTeamCustomRolesAsync(string, IReadOnlyList<TenantRoleDefinition>), which needs team:manage. Seeing which roles a team defines is part of seeing the team.

GetTeamKeyByInviteKeyAsync(string)

The key of the team holding an outstanding invitation whose code is inviteKey, or null.

public Task<string> GetTeamKeyByInviteKeyAsync(string inviteKey)

Parameters

inviteKey string

Returns

Task<string>

Remarks

Returns a key rather than a team, so the caller loads it through the reads that already exist and this adds no second way to fetch a team. It also keeps the member type out of the signature, which a generic overload would have dragged in for no gain.

Null means "no single team", covering three cases on purpose: nothing matched, the store cannot answer this at all, and — the one worth stating — more than one team matched. A token is a bearer credential, so resolving an ambiguous one by picking a team would hand someone access to a team nobody invited them to.

The default returns null, so a host with its own store keeps compiling and keeps working: invitation links carrying the team key still resolve, and only the short form is unavailable.

GetTeamKeyByInviteKeyInternalAsync(string)

The outstanding invitation matching inviteKey on teamKey, or null. Backs expiry enforcement.

protected virtual Task<string> GetTeamKeyByInviteKeyInternalAsync(string inviteKey)

Parameters

inviteKey string

Returns

Task<string>

Remarks

Virtual rather than abstract, like the other members added after the seam was first drawn, so a host with its own store keeps compiling. The default returns null, which reads as "no expiry to enforce".

That default is a hole if a lifetime is configured and this is not overridden — expiry would silently not apply. It is a startup check rather than a silent default for exactly that reason: see InvitationExpiryWiringCheck, which fails the boot naming this method when Lifetime is set and the store cannot answer it. Nothing is asked of a host that has not opted into expiry.

GetTeamMemberAsync(string, string)

One active member of a team. Requires team:read on that team.

public Task<ITeamMember> GetTeamMemberAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task<ITeamMember>

Remarks

Whether an invited or rejected member comes back here is up to the host's store, so do not rely on either answer. This resolves through the store's "teams this user belongs to" query. The MongoDB store filters that query on State == MembershipState.Member, which makes a pending invitee indistinguishable from somebody who was never in the team; a store written differently may well return them.

So treat a non-null result as "has some membership" and null as "cannot act as a member" — never as a reliable answer to which state they are in. Anything that must tell the states apart — a message explaining a refusal, a roster count, an admin grid — has to use GetMembersAsync(string), which reads the team directly and is the only portable way to see every state.

Not hypothetical: suspending a member shipped with this bug, refusing an invitee with "is not a member of team", which was both untrue and unhelpful.

GetTeamMembersAsync(string, string)

protected abstract Task<ITeamMember> GetTeamMembersAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task<ITeamMember>

GetTeamsAsync()

public IAsyncEnumerable<ITeam> GetTeamsAsync()

Returns

IAsyncEnumerable<ITeam>

GetTeamsAsync(IUser)

protected abstract IAsyncEnumerable<ITeam> GetTeamsAsync(IUser user)

Parameters

user IUser

Returns

IAsyncEnumerable<ITeam>

GetTeamsAsync<TMember>()

public IAsyncEnumerable<ITeam<TMember>> GetTeamsAsync<TMember>() where TMember : ITeamMember

Returns

IAsyncEnumerable<ITeam<TMember>>

Type Parameters

TMember

GetTeamsForUserWithAccessLevelAsync(string, AccessLevel)

The teams where this user holds exactly accessLevel. Requires the Manage system scope.

public Task<IReadOnlyList<ITeam>> GetTeamsForUserWithAccessLevelAsync(string userKey, AccessLevel accessLevel)

Parameters

userKey string
accessLevel AccessLevel

Returns

Task<IReadOnlyList<ITeam>>

Remarks

The driver is user deletion: asking for Owner answers "which teams will this delete strand?", so the admin can transfer ownership before deleting rather than being told afterwards that something is now unrecoverable.

Exact match, not minimum. "Teams they own" is the question; a caller wanting a threshold can ask more than once. A minimum-level overload would make the common case ambiguous at the call site, where Owner would silently also mean every level above it — of which there are none, making the parameter read as if it did something it does not.

Gated on users:manage rather than teams:read, deliberately. The caller of this already holds the right to remove the user from every one of these teams, so learning which they are is strictly less than they can already do — and gating it on a scope they may not hold would hide the warning from exactly the person about to cause the damage.

GetTeamsForUserWithAccessLevelInternalAsync(string, AccessLevel)

Backs RemoveUserFromAllTeamsAsync(string). Virtual rather than abstract so existing derived services keep compiling; the default throws rather than returning 0, since a silent no-op on a deletion path would hide the missing implementation. Storage-backed bases override it.

protected virtual Task<IReadOnlyList<ITeam>> GetTeamsForUserWithAccessLevelInternalAsync(string userKey, AccessLevel accessLevel)

Parameters

userKey string
accessLevel AccessLevel

Returns

Task<IReadOnlyList<ITeam>>

IsTeamKeyInUseAsync(string)

Whether a team key is already taken — including by a soft-deleted team.

protected virtual Task<bool> IsTeamKeyInUseAsync(string teamKey)

Parameters

teamKey string

Returns

Task<bool>

Remarks

A soft-deleted team still owns its key, and this is what enforces that. Key generation used the ordinary team read, which now excludes deleted teams — so without this a deleted team's key reads as free and gets reissued. In a deployment that derives a team's database name from its key, the new team would then be pointed at the deleted team's data: the corruption Tharga/Team#224 is about, arriving by a different route.

The default keeps the old behaviour — live teams only — so a store that cannot see deleted teams is unaffected, which is also the store that cannot soft-delete and therefore never has one.

LeaveTeamAsync(string)

Removes the calling user from the team. Refuses the Owner, who must transfer ownership first, and the last administrator of a team with no owner.

public Task LeaveTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task

Remarks

It takes no user key, and that is the whole of its authorization. There is nothing in the signature to point at anybody else, so a caller can only remove themselves — which is why the decorators pass it through unscoped where RemoveMemberAsync(string, string) requires member:manage. That scope is registered at Administrator, so gating leaving on it means an ordinary member cannot leave the team they are in.

A scope could not express this rule anyway: a suspended member holds none at all, and suspension is precisely the state someone most wants out of.

A default interface method for the same reason as RestoreTeamAsync<TMember>(string) — a host implementing this contract directly keeps compiling — and it throws rather than no-opping, because reporting success for a departure that did not happen leaves someone believing they are out of a team they are still in.

PurgeTeamAsync(string)

Removes the team permanently, including its storage.

protected virtual Task PurgeTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task

Remarks

The default is DeleteTeamAsync(string) — precisely what deleting a team meant before soft delete existed — so every store gains a working purge without implementing anything.

This is the only operation that needs whatever privilege the adapter requires to drop a team's data. For the MongoDB adapter in a per-team-database deployment that is dropDatabase, which is why confining it here is the point of Tharga/Team#224.

PurgeTeamAsync<TMember>(string)

Removes a team permanently, including its storage. Irreversible.

public Task PurgeTeamAsync<TMember>(string teamKey) where TMember : ITeamMember

Parameters

teamKey string

Returns

Task

Type Parameters

TMember

Remarks

The only operation needing the privilege to drop a team's data. A store refusal surfaces as TeamStorageException rather than as the store's own exception.

RemoveMemberAsync(string, string)

public Task RemoveMemberAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task

RemoveTeamMemberAsync(string, string)

protected abstract Task RemoveTeamMemberAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task

RemoveUserFromAllTeamsAsync(string)

Removes the user from every team they appear in, regardless of membership state. Backs user deletion (DeleteUserAsync(string, bool, CancellationToken)); requires the Manage system scope. Returns the number of teams the user was removed from.

public Task<int> RemoveUserFromAllTeamsAsync(string userKey)

Parameters

userKey string

Returns

Task<int>

RemoveUserFromAllTeamsInternalAsync(string)

protected virtual Task<int> RemoveUserFromAllTeamsInternalAsync(string userKey)

Parameters

userKey string

Returns

Task<int>

RenameTeamAsync<TMember>(string, string)

public Task RenameTeamAsync<TMember>(string teamKey, string name) where TMember : ITeamMember

Parameters

teamKey string
name string

Returns

Task

Type Parameters

TMember

ResolveDisplayName(IUser)

public static string ResolveDisplayName(IUser user)

Parameters

user IUser

Returns

string

RestoreTeamAsync(string)

Clears the deleted mark, returning the team to normal use.

protected virtual Task RestoreTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task

Remarks

Unreachable unless SupportsSoftDelete is true — there is nothing to restore in a store that deletes outright — so the default throws rather than pretending to succeed.

RestoreTeamAsync<TMember>(string)

Restores a soft-deleted team.

public Task RestoreTeamAsync<TMember>(string teamKey) where TMember : ITeamMember

Parameters

teamKey string

Returns

Task

Type Parameters

TMember

SetInvitationResponseAsync(string, string, string, bool)

public Task SetInvitationResponseAsync(string teamKey, string userKey, string inviteKey, bool accept)

Parameters

teamKey string
userKey string
inviteKey string
accept bool

Returns

Task

SetMemberLastSeenAsync(string)

public Task SetMemberLastSeenAsync(string teamKey)

Parameters

teamKey string

Returns

Task

SetMemberNameAsync(string, string, string)

public Task SetMemberNameAsync(string teamKey, string userKey, string name)

Parameters

teamKey string
userKey string
name string

Returns

Task

SetMemberRoleAsync(string, string, AccessLevel)

Sets a member's access level. Ownership is not settable here — it changes only through TransferOwnershipAsync<TMember>(string, string), which checks that the caller is the current owner.

public Task SetMemberRoleAsync(string teamKey, string userKey, AccessLevel accessLevel)

Parameters

teamKey string
userKey string
accessLevel AccessLevel

Returns

Task

Remarks

Both directions are refused. Granting Owner would let any holder of the member-manage scope promote themselves past that check; demoting the sitting owner would leave a team nobody can transfer, because transfer requires the caller to be the owner. Transfer itself is unaffected — it calls the protected SetTeamMemberRoleAsync(string, string, AccessLevel) directly.

SetMemberScopeOverridesAsync(string, string, string[])

public Task SetMemberScopeOverridesAsync(string teamKey, string userKey, string[] scopeOverrides)

Parameters

teamKey string
userKey string
scopeOverrides string[]

Returns

Task

SetMemberSuspendedAsync(string, string, bool)

public Task SetMemberSuspendedAsync(string teamKey, string userKey, bool suspended)

Parameters

teamKey string
userKey string
suspended bool

Returns

Task

Remarks

Two refusals, both mirroring guards this class already applies elsewhere. The Owner cannot be suspended — the same reason the owner cannot leave and cannot be demoted: it would leave a team whose ownership nobody can transfer, since transfer requires the caller to be the owner. A member cannot suspend themselves, so an administrator who does it needs a second one to undo it, and somebody is always left holding member:manage.

The member cache is dropped on both directions, or the claims builder keeps reading the old state and the suspension takes effect only after the entry ages out.

SetMemberTenantRolesAsync(string, string, string[])

public Task SetMemberTenantRolesAsync(string teamKey, string userKey, string[] tenantRoles)

Parameters

teamKey string
userKey string
tenantRoles string[]

Returns

Task

SetOwnerAsync<TMember>(string, string)

Makes an existing member the sole owner of the team, demoting every other owner to Administrator. Requires the SetOwner system scope. Returns the user keys of the owners demoted, empty when nothing changed.

public Task<SetOwnerResult> SetOwnerAsync<TMember>(string teamKey, string newOwnerUserKey) where TMember : ITeamMember

Parameters

teamKey string
newOwnerUserKey string

Returns

Task<SetOwnerResult>

Type Parameters

TMember

Remarks

Refuses only when the candidate is not already a member — see TeamOwnership for why that one condition is load-bearing and why the current owner count deliberately is not. Serves three states: a team with several owners (a legacy sync), a team whose owner cannot hand over themselves, and a team with no owner at all.

Distinct from TransferOwnershipAsync<TMember>(string, string), which requires the caller to be the owner. That is the in-team path and stays as it is; this is the operator path.

SetTeamConsentAsync(string, string[], AccessLevel?)

public Task SetTeamConsentAsync(string teamKey, string[] consentedRoles, AccessLevel? accessLevel = null)

Parameters

teamKey string
consentedRoles string[]
accessLevel AccessLevel?

Returns

Task

SetTeamConsentInternalAsync(string, string[], AccessLevel?)

protected abstract Task SetTeamConsentInternalAsync(string teamKey, string[] consentedRoles, AccessLevel? accessLevel)

Parameters

teamKey string
consentedRoles string[]
accessLevel AccessLevel?

Returns

Task

SetTeamCustomRolesAsync(string, IReadOnlyList<TenantRoleDefinition>)

public Task SetTeamCustomRolesAsync(string teamKey, IReadOnlyList<TenantRoleDefinition> customRoles)

Parameters

teamKey string
customRoles IReadOnlyList<TenantRoleDefinition>

Returns

Task

SetTeamCustomRolesInternalAsync(string, IReadOnlyList<TenantRoleDefinition>)

protected abstract Task SetTeamCustomRolesInternalAsync(string teamKey, IReadOnlyList<TenantRoleDefinition> customRoles)

Parameters

teamKey string
customRoles IReadOnlyList<TenantRoleDefinition>

Returns

Task

SetTeamIconAsync(string, byte[], string)

Sets the team's icon from raw image bytes: stores them via the registered IIconStore, persists the reference on the team, and deletes any previously-stored icon. Requires a registered icon store. Gated by team:manage.

public Task SetTeamIconAsync(string teamKey, byte[] data, string contentType)

Parameters

teamKey string
data byte[]
contentType string

Returns

Task

SetTeamIconReferenceInternalAsync(string, string)

Backs SetTeamIconAsync(string, byte[], string) / ClearTeamIconAsync(string) — persists the icon reference (or null to clear) on the team document. Virtual-throw so existing derived services keep compiling; storage-backed bases override it.

protected virtual Task SetTeamIconReferenceInternalAsync(string teamKey, string reference)

Parameters

teamKey string
reference string

Returns

Task

SetTeamMemberInvitationExpiryAsync(string, string, DateTime?)

Moves the expiry of the invitation matching inviteKey, leaving its code alone.

protected virtual Task SetTeamMemberInvitationExpiryAsync(string teamKey, string inviteKey, DateTime? expiresAt)

Parameters

teamKey string
inviteKey string
expiresAt DateTime?

Returns

Task

Remarks

Throws rather than no-opping when unimplemented, unlike GetInvitationInternalAsync(string, string). The difference is what silence would mean: a store that cannot read an expiry has nothing to enforce, but a store that silently discards an extension reports success for an invitation that stays expired, and the operator finds out from the person who could not accept it.

SetTeamMemberInvitationResponseAsync(string, string, string, bool)

protected abstract Task<ITeam> SetTeamMemberInvitationResponseAsync(string teamKey, string userKey, string inviteKey, bool accept)

Parameters

teamKey string
userKey string
inviteKey string
accept bool

Returns

Task<ITeam>

SetTeamMemberLastSeenAsync(string, string)

protected abstract Task SetTeamMemberLastSeenAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task

SetTeamMemberNameAsync(string, string, string)

protected abstract Task SetTeamMemberNameAsync(string teamKey, string userKey, string name)

Parameters

teamKey string
userKey string
name string

Returns

Task

SetTeamMemberRoleAsync(string, string, AccessLevel)

protected abstract Task SetTeamMemberRoleAsync(string teamKey, string userKey, AccessLevel accessLevel)

Parameters

teamKey string
userKey string
accessLevel AccessLevel

Returns

Task

SetTeamMemberScopeOverridesAsync(string, string, string[])

protected abstract Task SetTeamMemberScopeOverridesAsync(string teamKey, string userKey, string[] scopeOverrides)

Parameters

teamKey string
userKey string
scopeOverrides string[]

Returns

Task

SetTeamMemberSuspendedAsync(string, string, DateTime?, string)

Persists a member's suspended state. Override to support suspending members.

protected virtual Task SetTeamMemberSuspendedAsync(string teamKey, string userKey, DateTime? suspendedAt, string suspendedBy)

Parameters

teamKey string
userKey string
suspendedAt DateTime?
suspendedBy string

Returns

Task

Remarks

Virtual with a throwing body rather than abstract: adding an abstract member here would break every host that already derives from this class. Throwing rather than no-opping for the same reason the user and key equivalents do — a suspension silently skipped is a containment reported but never applied.

SetTeamMemberTenantRolesAsync(string, string, string[])

protected abstract Task SetTeamMemberTenantRolesAsync(string teamKey, string userKey, string[] tenantRoles)

Parameters

teamKey string
userKey string
tenantRoles string[]

Returns

Task

SetTeamNameAsync(string, string)

protected abstract Task SetTeamNameAsync(string teamKey, string name)

Parameters

teamKey string
name string

Returns

Task

SoftDeleteTeamAsync(string, string)

Marks the team deleted without removing it, recording when and by whom.

protected virtual Task SoftDeleteTeamAsync(string teamKey, string deletedBy)

Parameters

teamKey string
deletedBy string

Returns

Task

Remarks

Virtual, never abstract — an abstract member here breaks every derived host at compile time. The default delegates to DeleteTeamAsync(string), so a store that has not implemented soft delete performs the irreversible delete it always did rather than throwing. Paired with SupportsSoftDelete, which tells callers which of the two they will get.

TransferOwnershipAsync<TMember>(string, string)

public Task TransferOwnershipAsync<TMember>(string teamKey, string newOwnerUserKey) where TMember : ITeamMember

Parameters

teamKey string
newOwnerUserKey string

Returns

Task

Type Parameters

TMember

Events

SelectTeamEvent

public event EventHandler<SelectTeamEventArgs> SelectTeamEvent

Event Type

EventHandler<SelectTeamEventArgs>

TeamsListChangedEvent

public event EventHandler<TeamsListChangedEventArgs> TeamsListChangedEvent

Event Type

EventHandler<TeamsListChangedEventArgs>