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

AddTeamMemberAsync(string, InviteUserModel)

protected override Task AddTeamMemberAsync(string teamKey, InviteUserModel model)

Parameters

teamKey string
model InviteUserModel

Returns

Task

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>

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(). Virtual rather than abstract so existing derived services keep compiling; the default returns nothing, and storage-backed bases override it.

protected override IAsyncEnumerable<ITeam> GetAllTeamsInternalAsync()

Returns

IAsyncEnumerable<ITeam>

GetConsentedTeamsInternalAsync(string[])

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

Parameters

userRoles string[]

Returns

IAsyncEnumerable<ITeam>

GetInvitationInternalAsync(string, string)

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

Parameters

teamKey string
inviteKey string

Returns

Task<string>

GetTeamAsync(string)

protected override Task<ITeam> GetTeamAsync(string teamKey)

Parameters

teamKey string

Returns

Task<ITeam>

GetTeamKeyByInviteKeyInternalAsync(string)

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

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

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

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). 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 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

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.