Files
OTSSignsOrchestrator/OTSSignsOrchestrator.Server/Clients/IXiboApiClient.cs
Matt Batchelder c6d46098dd feat: Implement provisioning pipelines for subscription management
- Add ReactivatePipeline to handle subscription reactivation, including scaling Docker services, health verification, status updates, audit logging, and broadcasting status changes.
- Introduce RotateCredentialsPipeline for OAuth2 credential rotation, managing the deletion of old apps, creation of new ones, credential storage, access verification, and audit logging.
- Create StepRunner to manage job step execution, including lifecycle management and progress broadcasting via SignalR.
- Implement SuspendPipeline for subscription suspension, scaling down services, updating statuses, logging audits, and broadcasting changes.
- Add UpdateScreenLimitPipeline to update Xibo CMS screen limits and record snapshots.
- Introduce XiboFeatureManifests for hardcoded feature ACLs per role.
- Add docker-compose.dev.yml for local development with PostgreSQL setup.
2026-03-18 10:27:26 -04:00

139 lines
5.5 KiB
C#

using Refit;
namespace OTSSignsOrchestrator.Server.Clients;
// ── Request DTOs ────────────────────────────────────────────────────────────
public record CreateUserRequest(
string UserName,
string Email,
string Password,
int UserTypeId,
int HomePageId);
public record UpdateUserRequest(
string? UserName,
string? Email,
string? Password,
int? UserTypeId,
int? HomePageId,
int? Retired);
public record CreateGroupRequest(string Group, string? Description);
public record AssignMemberRequest(int[] UserId);
public record SetAclRequest(string[] ObjectId, string[] PermissionsId);
public record CreateApplicationRequest(string Name);
public record UpdateSettingsRequest(Dictionary<string, string> Settings);
public record CreateDisplayRequest(string Display, string? Description);
// ── Xibo CMS Refit Interface ────────────────────────────────────────────────
// CRITICAL: GET /api/application is BLOCKED — only POST and DELETE exist.
// All group endpoints use /api/group, NOT /api/usergroup.
// Feature assignment is POST /api/group/{id}/acl, NOT /features.
// Xibo paginates at 10 items by default — always pass start + length params.
[Headers("Authorization: Bearer")]
public interface IXiboApiClient
{
// ── About ───────────────────────────────────────────────────────────────
[Get("/about")]
Task<ApiResponse<object>> GetAboutAsync();
// ── Users ───────────────────────────────────────────────────────────────
[Get("/user")]
Task<List<Dictionary<string, object>>> GetUsersAsync(
[AliasAs("start")] int? start = 0,
[AliasAs("length")] int? length = 200);
[Post("/user")]
Task<ApiResponse<Dictionary<string, object>>> CreateUserAsync(
[Body(BodySerializationMethod.UrlEncoded)] CreateUserRequest body);
[Put("/user/{userId}")]
Task<ApiResponse<Dictionary<string, object>>> UpdateUserAsync(
int userId,
[Body(BodySerializationMethod.UrlEncoded)] UpdateUserRequest body);
[Delete("/user/{userId}")]
Task DeleteUserAsync(int userId);
// ── Groups (NOT /usergroup) ─────────────────────────────────────────────
[Get("/group")]
Task<List<Dictionary<string, object>>> GetGroupsAsync(
[AliasAs("start")] int? start = 0,
[AliasAs("length")] int? length = 200);
[Post("/group")]
Task<ApiResponse<Dictionary<string, object>>> CreateGroupAsync(
[Body(BodySerializationMethod.UrlEncoded)] CreateGroupRequest body);
[Delete("/group/{groupId}")]
Task DeleteGroupAsync(int groupId);
[Post("/group/members/assign/{groupId}")]
Task<ApiResponse<object>> AssignUserToGroupAsync(
int groupId,
[Body(BodySerializationMethod.UrlEncoded)] AssignMemberRequest body);
// ACL — NOT /features
[Post("/group/{groupId}/acl")]
Task<ApiResponse<object>> SetGroupAclAsync(
int groupId,
[Body(BodySerializationMethod.UrlEncoded)] SetAclRequest body);
// ── Displays ────────────────────────────────────────────────────────────
[Get("/display")]
Task<List<Dictionary<string, object>>> GetDisplaysAsync(
[AliasAs("start")] int? start = 0,
[AliasAs("length")] int? length = 200,
[AliasAs("authorised")] int? authorised = null);
// ── Applications (POST + DELETE only — GET is BLOCKED) ──────────────────
[Post("/application")]
Task<ApiResponse<Dictionary<string, object>>> CreateApplicationAsync(
[Body(BodySerializationMethod.UrlEncoded)] CreateApplicationRequest body);
[Delete("/application/{key}")]
Task DeleteApplicationAsync(string key);
// ── Settings ────────────────────────────────────────────────────────────
[Get("/settings")]
Task<ApiResponse<object>> GetSettingsAsync();
[Put("/settings")]
Task<ApiResponse<object>> UpdateSettingsAsync(
[Body(BodySerializationMethod.UrlEncoded)] UpdateSettingsRequest body);
}
// ── Pagination helper ───────────────────────────────────────────────────────
public static class XiboApiClientExtensions
{
/// <summary>
/// Pages through a Xibo list endpoint until a page returns fewer items than pageSize.
/// </summary>
public static async Task<List<T>> GetAllPagesAsync<T>(
this IXiboApiClient client,
Func<int, int, Task<List<T>>> listMethod,
int pageSize = 200)
{
var all = new List<T>();
var start = 0;
while (true)
{
var page = await listMethod(start, pageSize);
all.AddRange(page);
if (page.Count < pageSize)
break;
start += pageSize;
}
return all;
}
}