Interface ISupportCaseService
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
caseIdstringteamKeystringcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcancellationTokenCancellationToken
Returns
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
teamKeystringcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcancellationTokenCancellationToken
Returns
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
teamKeystringcursorstringpageSizeintcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcursorstringpageSizeintcancellationTokenCancellationToken
Returns
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
teamKeystringcursorstringpageSizeintcancellationTokenCancellationToken
Returns
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
teamKeystringcancellationTokenCancellationToken
Returns
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
cursorstringpageSizeintcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcancellationTokenCancellationToken
Returns
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
teamKeystringsubjectstringbodystringassistanceSupportAssistancecancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringbodystringcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcancellationTokenCancellationToken
Returns
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
teamKeystringcaseIdstringcancellationTokenCancellationToken
Returns
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.