Files
Swarm/DysonNetwork.Pass/Account/PresenceActivityController.cs

267 lines
9.9 KiB
C#

using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Pass.Account;
/// <summary>
/// Controller for managing user presence activities with lease-based expiration.
/// Supports both user-defined manual IDs and autogenerated GUIDs for activity management.
/// </summary>
[ApiController]
[Route("/api/activities")]
public class PresenceActivityController(AppDatabase db, AccountEventService service)
: ControllerBase
{
/// <summary>
/// Retrieves all active (non-expired) presence activities for the authenticated user.
/// </summary>
/// <returns>List of active presence activities</returns>
[HttpGet]
[ProducesResponseType<List<SnPresenceActivity>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<List<SnPresenceActivity>>> GetActivities()
{
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized();
var activities = await service.GetActiveActivities(currentUser.Id);
return Ok(activities);
}
/// <summary>
/// Retrieves active presence activities for any user account (admin/debugging endpoint).
/// </summary>
/// <returns>List of active presence activities</returns>
[HttpGet("{identifier}")]
[ProducesResponseType<List<SnPresenceActivity>>(StatusCodes.Status200OK)]
public async Task<ActionResult<List<SnPresenceActivity>>> GetActivitiesByAccountId(
string identifier
)
{
var account = Guid.TryParse(identifier, out var identifierGuid)
? await db.Accounts.FirstOrDefaultAsync(a => a.Id == identifierGuid)
: await db.Accounts.FirstOrDefaultAsync(a => a.Name == identifier);
if (account is null)
return NotFound();
var activities = await service.GetActiveActivities(account.Id);
return Ok(activities);
}
/// <summary>
/// Creates a new presence activity with lease expiration.
/// </summary>
/// <param name="request">Activity creation parameters</param>
/// <returns>The created activity</returns>
[HttpPost]
[ProducesResponseType<SnPresenceActivity>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<SnPresenceActivity>> SetActivity(
[FromBody] SetActivityRequest request
)
{
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized();
var activity = new SnPresenceActivity
{
Type = request.Type,
ManualId = request.ManualId,
Title = request.Title,
Subtitle = request.Subtitle,
Caption = request.Caption,
Meta = request.Meta,
AccountId = currentUser.Id,
};
var result = await service.SetActivity(activity, request.LeaseMinutes);
return Ok(result);
}
/// <summary>
/// Updates an existing presence activity using either its GUID or manual ID.
/// </summary>
/// <param name="id">System-generated GUID of the activity (optional)</param>
/// <param name="manualId">User-defined manual ID of the activity (optional)</param>
/// <param name="request">Update parameters (only provided fields are updated)</param>
/// <returns>The updated activity</returns>
/// <remarks>One of 'id' or 'manualId' must be provided and non-empty.</remarks>
[HttpPut]
[ProducesResponseType<SnPresenceActivity>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<SnPresenceActivity>> UpdateActivity(
[FromQuery] string? id,
[FromQuery] string? manualId,
[FromBody] UpdateActivityRequest request
)
{
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized();
var type = request.Type;
var title = request.Title;
var subtitle = request.Subtitle;
var caption = request.Caption;
var requestManualId = request.ManualId;
var requestMeta = request.Meta;
var leaseMinutes = request.LeaseMinutes;
if (!string.IsNullOrWhiteSpace(manualId))
{
var result = await service.UpdateActivityByManualId(
manualId,
currentUser.Id,
activity =>
{
if (type.HasValue)
activity.Type = type.Value;
if (title != null)
activity.Title = title;
if (subtitle != null)
activity.Subtitle = subtitle;
if (caption != null)
activity.Caption = caption;
if (requestManualId != null)
activity.ManualId = requestManualId;
if (requestMeta != null)
activity.Meta = requestMeta;
},
leaseMinutes
);
if (result == null)
return NotFound();
return Ok(result);
}
else if (!string.IsNullOrWhiteSpace(id) && Guid.TryParse(id, out var activityGuid))
{
var result = await service.UpdateActivity(
activityGuid,
activity =>
{
if (type.HasValue)
activity.Type = type.Value;
if (title != null)
activity.Title = title;
if (subtitle != null)
activity.Subtitle = subtitle;
if (caption != null)
activity.Caption = caption;
if (requestManualId != null)
activity.ManualId = requestManualId;
if (requestMeta != null)
activity.Meta = requestMeta;
},
leaseMinutes
);
return Ok(result);
}
else
{
return BadRequest("Either 'id' (GUID) or 'manualId' must be provided");
}
}
/// <summary>
/// Deletes a presence activity using either its GUID or manual ID.
/// </summary>
/// <param name="id">System-generated GUID of the activity (optional)</param>
/// <param name="manualId">User-defined manual ID of the activity (optional)</param>
/// <returns>NoContent on success</returns>
/// <remarks>One of 'id' or 'manualId' must be provided and non-empty. Soft-deletes the activity.</remarks>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> DeleteActivityById(
[FromQuery] string? id,
[FromQuery] string? manualId
)
{
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
return Unauthorized();
if (!string.IsNullOrWhiteSpace(manualId))
{
var deleted = await service.DeleteActivityByManualId(manualId, currentUser.Id);
if (!deleted)
return NotFound();
return NoContent();
}
else
{
if (!string.IsNullOrWhiteSpace(id) && Guid.TryParse(id, out var activityGuid))
{
var deleted = await service.DeleteActivity(activityGuid);
if (!deleted)
return NotFound();
return NoContent();
}
return BadRequest("Either 'id' (GUID) or 'manualId' must be provided");
}
}
/// <summary>
/// Request model for creating a new presence activity.
/// </summary>
public class SetActivityRequest
{
/// <summary>The type of presence activity (e.g., Gaming, Music, Workout)</summary>
public PresenceType Type { get; set; }
/// <summary>User-defined identifier for the activity (optional, for easy reference)</summary>
public string? ManualId { get; set; }
/// <summary>Main title of the activity</summary>
public string? Title { get; set; }
/// <summary>Secondary subtitle of the activity</summary>
public string? Subtitle { get; set; }
/// <summary>Additional caption/description</summary>
public string? Caption { get; set; }
/// <summary>Extensible metadata dictionary for custom developer data</summary>
public Dictionary<string, object>? Meta { get; set; }
/// <summary>Lease duration in minutes (1-60, default: 5)</summary>
public int LeaseMinutes { get; set; } = 5;
}
/// <summary>
/// Request model for updating an existing presence activity.
/// All fields are optional and will only update if provided.
/// </summary>
public class UpdateActivityRequest
{
/// <summary>The type of presence activity (optional update)</summary>
public PresenceType? Type { get; set; }
/// <summary>User-defined identifier update</summary>
public string? ManualId { get; set; }
/// <summary>Title update</summary>
public string? Title { get; set; }
/// <summary>Subtitle update</summary>
public string? Subtitle { get; set; }
/// <summary>Caption update</summary>
public string? Caption { get; set; }
/// <summary>Metadata update</summary>
public Dictionary<string, object>? Meta { get; set; }
/// <summary>Lease renewal in minutes</summary>
public int? LeaseMinutes { get; set; }
}
}