Table of Contents

Interface ISupportCaseService

Namespace
Tharga.Team.Support.Cases
Assembly
Tharga.Team.dll

Support-case operations. The surface a host, a component or a channel adapter calls.

public interface ISupportCaseService

Remarks

Operations, not CRUD. There is no UpdateCase: raising, replying and closing are three distinct facts, each separately authorizable and separately auditable. A general update would be none of those, because its legitimacy would depend on which of the three it was really doing.

Every method names its team. A case id alone never identifies a case — the store requires the team too — so a caller cannot reach another tenant's case by holding an id, and the authorization decorator has a team to check against on every call.

Authorization is applied by the decorator over this interface and nowhere else. Implementations of this interface enforce nothing, and a component that renders a button still has its call checked.

Methods

AssignCaseAsync(string, string, CancellationToken)

Gives an unassigned case to a team.

Task<bool> AssignCaseAsync(string caseId, string teamKey, CancellationToken cancellationToken = default)

Parameters

caseId string
teamKey string
cancellationToken CancellationToken

Returns

Task<bool>

Remarks

The only way a case gains a team after the fact, and an operation rather than a property set because it is one authorizable, auditable fact: it decides which tenant a case and its entire transcript become part of.

Authorized by Manage, not by a scope on the receiving team. A member of one team must not be able to pull a case that may concern another into their own.

Only an unassigned case can be assigned. Moving a case between tenants moves its transcript with it, so it is refused rather than treated as a correction — if it is genuinely wrong, that is a decision somebody should have to make deliberately, and there is no evidence yet that anybody needs to.

CloseCaseAsync(string, string, CancellationToken)

Closes a case and records who closed it in its transcript.

Task CloseCaseAsync(string teamKey, string caseId, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cancellationToken CancellationToken

Returns

Task

GetAwaitingSupportCountAsync(string, CancellationToken)

How many open cases in the team are waiting on an answer — their newest entry came from the person who raised them. What a support-side indicator shows.

Task<int> GetAwaitingSupportCountAsync(string teamKey, CancellationToken cancellationToken = default)

Parameters

teamKey string
cancellationToken CancellationToken

Returns

Task<int>

Remarks

Counts across everybody's cases, so it is exactly as privileged as reading them and requires support:read.

GetCaseAsync(string, string, CancellationToken)

One case, or null when the team has no such case.

Task<SupportCase> GetCaseAsync(string teamKey, string caseId, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cancellationToken CancellationToken

Returns

Task<SupportCase>

GetCasesAsync(string, string, int, CancellationToken)

Every case in a team, newest first.

Task<SupportCasePage> GetCasesAsync(string teamKey, string cursor = null, int pageSize = 20, CancellationToken cancellationToken = default)

Parameters

teamKey string
cursor string
pageSize int
cancellationToken CancellationToken

Returns

Task<SupportCasePage>

GetMessagesAsync(string, string, string, int, CancellationToken)

A case's transcript, oldest first.

Task<SupportMessagePage> GetMessagesAsync(string teamKey, string caseId, string cursor = null, int pageSize = 50, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cursor string
pageSize int
cancellationToken CancellationToken

Returns

Task<SupportMessagePage>

GetMyCasesAsync(string, string, int, CancellationToken)

The caller's own cases in a team, newest first.

Task<SupportCasePage> GetMyCasesAsync(string teamKey, string cursor = null, int pageSize = 20, CancellationToken cancellationToken = default)

Parameters

teamKey string
cursor string
pageSize int
cancellationToken CancellationToken

Returns

Task<SupportCasePage>

GetMyUnreadCountAsync(string, CancellationToken)

How many of the caller's own cases hold entries they have not read. What a per-user indicator shows.

Task<int> GetMyUnreadCountAsync(string teamKey, CancellationToken cancellationToken = default)

Parameters

teamKey string
cancellationToken CancellationToken

Returns

Task<int>

GetUnassignedCasesAsync(string, int, CancellationToken)

Cases belonging to no team, for whoever triages them.

Task<SupportCasePage> GetUnassignedCasesAsync(string cursor = null, int pageSize = 20, CancellationToken cancellationToken = default)

Parameters

cursor string
pageSize int
cancellationToken CancellationToken

Returns

Task<SupportCasePage>

Remarks

Authorized by Read. Separate from GetCasesAsync(string, string, int, CancellationToken) because that one answers "what is happening in this team" and this one answers "what has arrived that nobody owns" — different questions, different grants.

MarkReadAsync(string, string, CancellationToken)

Records that the caller has read this case up to its newest entry.

Task MarkReadAsync(string teamKey, string caseId, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cancellationToken CancellationToken

Returns

Task

Remarks

Authorized exactly as reading the case is — anything weaker would let somebody write to a case they cannot see.

RaiseCaseAsync(string, string, string, SupportAssistance, CancellationToken)

Raises a case for a team, with its opening message.

Task<SupportCase> RaiseCaseAsync(string teamKey, string subject, string body, SupportAssistance assistance = SupportAssistance.None, CancellationToken cancellationToken = default)

Parameters

teamKey string
subject string
body string
assistance SupportAssistance
cancellationToken CancellationToken

Returns

Task<SupportCase>

Remarks

assistance is the customer's choice of who answers, and it is the only place that choice is made. Asking for an assistant when the host has registered none is not an error: the case is raised and a person answers it, which is what the customer would have got anyway.

ReopenCaseAsync(string, string, CancellationToken)

Opens a closed case again, keeping its history.

Task ReopenCaseAsync(string teamKey, string caseId, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cancellationToken CancellationToken

Returns

Task

Remarks

Authorized exactly as replying is — the member who raised the case, or a caller holding support:read or support:manage on the team. Somebody who could not answer a case has no business changing its state.

This is what makes closing safe to do. Without it, closing is a decision somebody has to be sure about, and the safe move is to leave cases open forever — which is how a case list stops being read. A case that closed too early costs one click to bring back, and it brings the conversation with it rather than starting a second case that explains nothing.

Reopening an already-open case does nothing and is not an error.

ReplyToCaseAsync(string, string, string, CancellationToken)

Appends a reply to an open case.

Task ReplyToCaseAsync(string teamKey, string caseId, string body, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
body string
cancellationToken CancellationToken

Returns

Task

RequestHumanAsync(string, string, CancellationToken)

Stops the assistant answering a case, so a person takes over.

Task RequestHumanAsync(string teamKey, string caseId, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cancellationToken CancellationToken

Returns

Task

Remarks

Same case, same transcript. Handing over is not a second case: whoever picks it up reads everything that was already said, which is the whole point of offering an assistant first.

Authorized exactly as replying is. On a case with no assistant this does nothing and is not an error, so a component can offer it without first working out whether it applies.

RunAssistantAsync(string, string, CancellationToken)

Has the assistant answer the newest message on a case, and appends what it says.

Task<bool> RunAssistantAsync(string teamKey, string caseId, CancellationToken cancellationToken = default)

Parameters

teamKey string
caseId string
cancellationToken CancellationToken

Returns

Task<bool>

True when an answer was appended.

Remarks

A separate operation rather than something raising and replying do for you. A model can take tens of seconds, and burying that inside a write would make reporting a problem feel broken. Raising stays fast, and the caller decides when to ask for the answer — which also lets a UI say that the assistant is thinking.

Does nothing and returns false when the case has no assistant, when the customer has already asked for a person, or when the host registered no responder. Every one of those is an ordinary state rather than an error, so a caller can call this unconditionally.