261 lines
9.9 KiB
C#
261 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 active (non-expired) presence activities for the authenticated user.
|
|
/// Optionally includes expired activities if includeExpired is true.
|
|
/// </summary>
|
|
/// <param name="includeExpired">Whether to include expired activities</param>
|
|
/// <param name="offset">The number of activities to skip for pagination</param>
|
|
/// <param name="take">The maximum number of activities to return</param>
|
|
/// <returns>List of presence activities</returns>
|
|
[HttpGet]
|
|
[ProducesResponseType<List<SnPresenceActivity>>(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
public async Task<ActionResult<List<SnPresenceActivity>>> GetActivities(
|
|
[FromQuery] bool includeExpired = false,
|
|
[FromQuery] int offset = 0,
|
|
[FromQuery] int take = 20
|
|
)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
|
|
return Unauthorized();
|
|
List<SnPresenceActivity> activities;
|
|
if (includeExpired)
|
|
{
|
|
(activities, var total) = await service.GetAllActivities(currentUser.Id, offset, take);
|
|
Response.Headers["X-Total"] = total.ToString();
|
|
}
|
|
else
|
|
{
|
|
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 or updates a presence activity with lease expiration.
|
|
/// If an activity with the same 'manualId' exists, it will be updated.
|
|
/// Otherwise, a new activity will be created.
|
|
/// </summary>
|
|
/// <param name="request">Activity creation or update parameters</param>
|
|
/// <returns>The created or updated 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();
|
|
|
|
if (!string.IsNullOrWhiteSpace(request.ManualId))
|
|
{
|
|
var result = await service.UpdateActivityByManualId(
|
|
request.ManualId,
|
|
currentUser.Id,
|
|
activity =>
|
|
{
|
|
if (request.Type.HasValue)
|
|
activity.Type = request.Type.Value;
|
|
activity.Title = request.Title;
|
|
activity.Subtitle = request.Subtitle;
|
|
activity.Caption = request.Caption;
|
|
activity.LargeImage = request.LargeImage;
|
|
activity.SmallImage = request.SmallImage;
|
|
activity.TitleUrl = request.TitleUrl;
|
|
activity.SubtitleUrl = request.SubtitleUrl;
|
|
activity.Meta = request.Meta;
|
|
},
|
|
request.LeaseMinutes
|
|
);
|
|
|
|
if (result != null)
|
|
{
|
|
return Ok(result);
|
|
}
|
|
}
|
|
|
|
if (!request.Type.HasValue)
|
|
return BadRequest("Type is required when creating a new activity");
|
|
|
|
var newActivity = new SnPresenceActivity
|
|
{
|
|
AccountId = currentUser.Id,
|
|
Type = request.Type.Value,
|
|
ManualId = request.ManualId,
|
|
Title = request.Title,
|
|
Subtitle = request.Subtitle,
|
|
Caption = request.Caption,
|
|
LargeImage = request.LargeImage,
|
|
SmallImage = request.SmallImage,
|
|
TitleUrl = request.TitleUrl,
|
|
SubtitleUrl = request.SubtitleUrl,
|
|
Meta = request.Meta
|
|
};
|
|
|
|
var createResult = await service.SetActivity(newActivity, request.LeaseMinutes);
|
|
return Ok(createResult);
|
|
}
|
|
|
|
/// <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="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("{id:guid}")]
|
|
[ProducesResponseType<SnPresenceActivity>(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<SnPresenceActivity>> UpdateActivity(
|
|
[FromRoute] Guid id,
|
|
[FromBody] SetActivityRequest request
|
|
)
|
|
{
|
|
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser)
|
|
return Unauthorized();
|
|
|
|
var result = await service.UpdateActivity(
|
|
id,
|
|
currentUser.Id,
|
|
activity =>
|
|
{
|
|
if (request.Type.HasValue)
|
|
activity.Type = request.Type.Value;
|
|
if (request.Title != null)
|
|
activity.Title = request.Title;
|
|
if (request.Subtitle != null)
|
|
activity.Subtitle = request.Subtitle;
|
|
if (request.Caption != null)
|
|
activity.Caption = request.Caption;
|
|
if (request.ManualId != null)
|
|
activity.ManualId = request.ManualId;
|
|
if (request.Meta != null)
|
|
activity.Meta = request.Meta;
|
|
},
|
|
request.LeaseMinutes
|
|
);
|
|
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <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();
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(id) || !Guid.TryParse(id, out var activityGuid))
|
|
return BadRequest("Either 'id' (GUID) or 'manualId' must be provided");
|
|
{
|
|
var deleted = await service.DeleteActivity(activityGuid, currentUser.Id);
|
|
if (!deleted)
|
|
return NotFound();
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
}
|
|
|
|
/// <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>Large image URL or base64 string</summary>
|
|
public string? LargeImage { get; set; }
|
|
|
|
/// <summary>Small image URL or base64 string</summary>
|
|
public string? SmallImage { get; set; }
|
|
|
|
/// <summary>Title URL</summary>
|
|
public string? TitleUrl { get; set; }
|
|
|
|
/// <summary>Subtitle URL</summary>
|
|
public string? SubtitleUrl { 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;
|
|
}
|
|
}
|