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;
 | 
						|
    }
 | 
						|
}
 |