Table of Contents

Class TeamServiceRepositoryBase<TTeamEntity, TMember>

Namespace
Tharga.Team.MongoDB
Assembly
Tharga.Team.MongoDB.dll
public abstract class TeamServiceRepositoryBase<TTeamEntity, TMember> : TeamServiceBase where TTeamEntity : TeamEntityBase<TMember> where TMember : TeamMemberBase

Type Parameters

TTeamEntity
TMember
Inheritance
TeamServiceRepositoryBase<TTeamEntity, TMember>
Derived
Inherited Members

Constructors

TeamServiceRepositoryBase(IUserService, ITeamRepository<TTeamEntity, TMember>, IMongoDbServiceFactory, IIconStore, ITeamCache, InvitationOptions)

protected TeamServiceRepositoryBase(IUserService userService, ITeamRepository<TTeamEntity, TMember> teamRepository, IMongoDbServiceFactory mongoDbServiceFactory, IIconStore iconStore = null, ITeamCache cache = null, InvitationOptions invitationOptions = null)

Parameters

userService IUserService

Resolves the calling user.

teamRepository ITeamRepository<TTeamEntity, TMember>

The team collection this service reads and writes.

mongoDbServiceFactory IMongoDbServiceFactory

Used for the operations that go outside the repository.

iconStore IIconStore

Optional. Required only for team icons.

cache ITeamCache

Optional. Forward it from your own service's constructor when running more than one instance — left unforwarded, the claims-path lookups fall back to a process-local cache that cannot see another instance's writes. See ITeamCache.

invitationOptions InvitationOptions

Optional. Forward it from your own service's constructor if you configure an invitation lifetime — left unforwarded, this base receives the defaults and invitations never expire however the host configured them. The same forwarding hazard as cache.

Properties

SupportsSoftDelete

This store can soft-delete, so TeamDeleteMode.Soft takes effect.

protected override bool SupportsSoftDelete { get; }

Property Value

bool

Methods

AddAccessRequestAsync(string, TeamAccessRequest)

Stores a new access request on the team. Marks any other request pending from the same requester Cancelled, and keeps at most HistoryLimit requests, newest first.

protected override Task AddAccessRequestAsync(string teamKey, TeamAccessRequest request)

Parameters

teamKey string
request TeamAccessRequest

Returns

Task

Remarks

Virtual with a throwing body rather than abstract, so a host deriving from this class keeps compiling — the SetTeamMemberSuspendedAsync(string, string, DateTime?, string) pattern. Override to support access requests.

AddTeamMemberAsync(string, InviteUserModel)

protected override Task AddTeamMemberAsync(string teamKey, InviteUserModel model)

Parameters

teamKey string
model InviteUserModel

Returns

Task

ApproveAccessRequestAsync(string, string, string, DateTime, DateTime?, string[], AccessLevel, TemporaryConsent)

Approves a request in one write, only if it is still pending: marks it approved with grantedUntil, sets the team's consent to consentedRoles at accessLevel, and stores temporaryConsent — removing any stored one when it is null. Returns false when the request was no longer pending.

protected override Task<bool> ApproveAccessRequestAsync(string teamKey, string requestId, string decidedBy, DateTime decidedAt, DateTime? grantedUntil, string[] consentedRoles, AccessLevel accessLevel, TemporaryConsent temporaryConsent)

Parameters

teamKey string
requestId string
decidedBy string
decidedAt DateTime
grantedUntil DateTime?
consentedRoles string[]
accessLevel AccessLevel
temporaryConsent TemporaryConsent

Returns

Task<bool>

Remarks

One write because the consent and the request's status are one fact: a store that set consent and then failed to mark the request would grant access nobody can see was approved.

CreateTeam(string, string, IUser, string)

protected abstract Task<TTeamEntity> CreateTeam(string teamKey, string name, IUser user, string displayName)

Parameters

teamKey string
name string
user IUser
displayName string

Returns

Task<TTeamEntity>

CreateTeamAsync(string, string, IUser, string)

protected override Task<ITeam> CreateTeamAsync(string teamKey, string name, IUser user, string displayName)

Parameters

teamKey string
name string
user IUser
displayName string

Returns

Task<ITeam>

CreateTeamMember(InviteUserModel)

protected abstract Task<TMember> CreateTeamMember(InviteUserModel model)

Parameters

model InviteUserModel

Returns

Task<TMember>

DecideAccessRequestAsync(string, string, TeamAccessRequestStatus, string, DateTime)

Marks a request status — only if it is still pending. Returns false when it was not, so a decision racing another one does not overwrite it.

protected override Task<bool> DecideAccessRequestAsync(string teamKey, string requestId, TeamAccessRequestStatus status, string decidedBy, DateTime decidedAt)

Parameters

teamKey string
requestId string
status TeamAccessRequestStatus
decidedBy string
decidedAt DateTime

Returns

Task<bool>

DeleteTeamAsync(string)

Removes the team record, then drops the team's database.

protected override Task DeleteTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task

Remarks

The record goes first, and the order is the point. The two writes cannot be made atomic — one is a document delete, the other a database drop — so the only choice is which way a partial failure fails. Dropping first and then failing to delete the record leaves a live team pointing at deleted data: it still lists, still resolves, still authorizes, and every read against it returns empty. Deleting the record first leaves an orphaned database, which is inert and which a sweep can find. Reported by Eplicta FortDocs (Tharga/Team#224), where the drop happened to throw first and nothing was lost.

A drop failure is wrapped as TeamStorageException rather than allowed to surface as a driver exception — see that type for why the message addresses the deployment rather than the caller. The record is already gone at that point, which is the intended outcome: the team is deleted, and what remains is a database to clean up.

GetAllTeamsInternalAsync()

Backs GetAllTeamsAsync() — every team, regardless of membership, for callers holding Read. Virtual rather than abstract so existing derived services keep compiling; storage-backed bases override it.

protected override IAsyncEnumerable<ITeam> GetAllTeamsInternalAsync()

Returns

IAsyncEnumerable<ITeam>

Remarks

The default throws rather than returning empty. Read switches the consuming UI into cross-team oversight mode, which lists teams from here — so an empty default reads as "this caller belongs to no team" and the team page tells every user, Owners included, that they are not a member of one. It is reached only by callers the authorization decorator has already admitted with that scope, so a host that never grants it is unaffected.

GetConsentedTeamsInternalAsync(string[])

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

Parameters

userRoles string[]

Returns

IAsyncEnumerable<ITeam>

GetInvitationInternalAsync(string, string)

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

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

Parameters

teamKey string
inviteKey string

Returns

Task<Invitation>

Remarks

The default reads the roster through GetMembersAsync(string), so a store that exposes its members gets expiry enforced without an override. Override it when the store can answer more cheaply.

Null never reads as "not expired". With a Lifetime configured, accepting a code this cannot find is refused, because an invitation whose expiry cannot be read must not be treated as one that has none.

GetInvitedMemberNameAsync(string, string)

The admin-entered name of the member invited with inviteKey, captured before accept clears it so it can be promoted to User.Name.

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

Parameters

teamKey string
inviteKey string

Returns

Task<string>

Remarks

The default reads the roster through GetMembersAsync(string), so a store that exposes its members needs no override.

GetTeamAsync(string)

protected override Task<ITeam> GetTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task<ITeam>

GetTeamKeyByInviteKeyInternalAsync(string)

Backs GetTeamKeyByInviteKeyAsync(string). Null when nothing matches, when more than one team matches, or when this store cannot look an invitation up without its team.

protected override Task<string> GetTeamKeyByInviteKeyInternalAsync(string inviteKey)

Parameters

inviteKey string

Returns

Task<string>

Remarks

Virtual so a host with its own store keeps compiling, but the default is not a working one. Every invitation link the toolkit mints carries only its code, so a store that cannot answer this produces links none of which resolves. TeamServiceCompletenessCheck reports it at startup, naming this member (Tharga/Team#286).

GetTeamMembersAsync(string, string)

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

Parameters

teamKey string
userKey string

Returns

Task<ITeamMember>

GetTeamsAsync(IUser)

protected override IAsyncEnumerable<ITeam> GetTeamsAsync(IUser user)

Parameters

user IUser

Returns

IAsyncEnumerable<ITeam>

GetTeamsForUserWithAccessLevelInternalAsync(string, AccessLevel)

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

Parameters

userKey string
accessLevel AccessLevel

Returns

Task<IReadOnlyList<ITeam>>

Remarks

Filters the cross-team enumeration rather than adding a repository query. The set is bounded by the number of teams, this runs once per delete confirmation rather than per request, and a dedicated query would be a second place for the membership shape to be interpreted.

IsTeamKeyInUseAsync(string)

A key is in use while any team holds it, deleted or not, so a soft-deleted team keeps its key reserved until it is purged.

protected override Task<bool> IsTeamKeyInUseAsync(string teamKey)

Parameters

teamKey string

Returns

Task<bool>

PurgeTeamAsync(string)

Removes the team record and drops its database. The only path here that needs dropDatabase.

protected override Task PurgeTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task

Remarks

Same ordering and same wrapping as DeleteTeamAsync(string) — record first, so a drop failure leaves an orphaned database rather than a live team pointing at nothing.

RemoveTeamMemberAsync(string, string)

protected override Task RemoveTeamMemberAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task

RemoveTeamMemberGuardedAsync(string, string, bool)

Removes a member unless that member is the Owner and, when requireOtherAdministrator, unless no other active administrator or owner would remain, as one write that checks the rules itself. False when the write was refused.

protected override Task<bool> RemoveTeamMemberGuardedAsync(string teamKey, string userKey, bool requireOtherAdministrator)

Parameters

teamKey string
userKey string
requireOtherAdministrator bool

Returns

Task<bool>

Remarks

RemoveUserFromAllTeamsInternalAsync(string)

protected override Task<int> RemoveUserFromAllTeamsInternalAsync(string userKey)

Parameters

userKey string

Returns

Task<int>

RestoreTeamAsync(string)

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

protected override 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.

SetTeamConsentInternalAsync(string, string[], AccessLevel?)

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

Parameters

teamKey string
consentedRoles string[]
accessLevel AccessLevel?

Returns

Task

SetTeamCustomRolesInternalAsync(string, IReadOnlyList<TenantRoleDefinition>)

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

Parameters

teamKey string
customRoles IReadOnlyList<TenantRoleDefinition>

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 override 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 override 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) there is no roster-reading default to fall back on, since writing needs the store. A store that silently discarded an extension would report success for an invitation that stays expired, and the operator would find out from the person who could not accept it.

SetTeamMemberInvitationResponseAsync(string, string, string, bool)

protected override 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 override Task SetTeamMemberLastSeenAsync(string teamKey, string userKey)

Parameters

teamKey string
userKey string

Returns

Task

SetTeamMemberNameAsync(string, string, string)

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

Parameters

teamKey string
userKey string
name string

Returns

Task

SetTeamMemberRoleAsync(string, string, AccessLevel)

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

Parameters

teamKey string
userKey string
accessLevel AccessLevel

Returns

Task

SetTeamMemberRoleUnlessOwnerAsync(string, string, AccessLevel)

Sets a member's access level unless that member is the Owner, as one write that checks the rule itself. False when the write was refused.

protected override Task<bool> SetTeamMemberRoleUnlessOwnerAsync(string teamKey, string userKey, AccessLevel accessLevel)

Parameters

teamKey string
userKey string
accessLevel AccessLevel

Returns

Task<bool>

Remarks

The guard in SetMemberRoleAsync(string, string, AccessLevel) reads before it writes, so a transfer landing in between lets the demotion through and leaves a team with no owner (Tharga/Team#342). A store that can make the condition part of the write overrides this. Virtual, never abstract: the default is the unconditional write, which is what a host store did before, so deriving classes keep compiling and behaving.

SetTeamMemberScopeOverridesAsync(string, string, string[])

protected override 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 override 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 override Task SetTeamMemberTenantRolesAsync(string teamKey, string userKey, string[] tenantRoles)

Parameters

teamKey string
userKey string
tenantRoles string[]

Returns

Task

SetTeamNameAsync(string, string)

protected override 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 override 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.

TransferTeamOwnershipAsync(string, string, string)

Makes newOwnerUserKey the owner and currentOwnerKey an administrator, only while the latter is still the owner, as one write. False when it was refused.

protected override Task<bool> TransferTeamOwnershipAsync(string teamKey, string currentOwnerKey, string newOwnerUserKey)

Parameters

teamKey string
currentOwnerKey string
newOwnerUserKey string

Returns

Task<bool>

Remarks

The default is the two writes this class made before, promoting before demoting so a failure between them never leaves the team without an owner. See SetTeamMemberRoleUnlessOwnerAsync(string, string, AccessLevel).