using DysonNetwork.Shared.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DysonNetwork.Pass.Account;
///
/// Controller for managing user presence activities with lease-based expiration.
/// Supports both user-defined manual IDs and autogenerated GUIDs for activity management.
///
[ApiController]
[Route("/api/activities")]
[Authorize]
public class PresenceActivityController(AppDatabase db, AccountEventService service) : ControllerBase
{
///
/// Retrieves all active (non-expired) presence activities for the authenticated user.
///
/// List of active presence activities
[HttpGet]
[ProducesResponseType>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task>> GetActivities()
{
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
var activities = await service.GetActiveActivities(currentUser.Id);
return Ok(activities);
}
///
/// Retrieves active presence activities for any user account (admin/debugging endpoint).
///
/// The account ID to fetch activities for
/// List of active presence activities
[HttpGet("{accountId:guid}")]
[ProducesResponseType>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task>> GetActivitiesByAccountId(Guid accountId)
{
var activities = await service.GetActiveActivities(accountId);
return Ok(activities);
}
///
/// Creates a new presence activity with lease expiration.
///
/// Activity creation parameters
/// The created activity
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task> 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);
}
///
/// Updates an existing presence activity using either its GUID or manual ID.
///
/// System-generated GUID of the activity (optional)
/// User-defined manual ID of the activity (optional)
/// Update parameters (only provided fields are updated)
/// The updated activity
/// One of 'id' or 'manualId' must be provided and non-empty.
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task> 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");
}
}
///
/// Deletes a presence activity using either its GUID or manual ID.
///
/// System-generated GUID of the activity (optional)
/// User-defined manual ID of the activity (optional)
/// NoContent on success
/// One of 'id' or 'manualId' must be provided and non-empty. Soft-deletes the activity.
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task 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");
}
}
///
/// Request model for creating a new presence activity.
///
public class SetActivityRequest
{
/// The type of presence activity (e.g., Gaming, Music, Workout)
public PresenceType Type { get; set; }
/// User-defined identifier for the activity (optional, for easy reference)
public string? ManualId { get; set; }
/// Main title of the activity
public string? Title { get; set; }
/// Secondary subtitle of the activity
public string? Subtitle { get; set; }
/// Additional caption/description
public string? Caption { get; set; }
/// Extensible metadata dictionary for custom developer data
public Dictionary? Meta { get; set; }
/// Lease duration in minutes (1-60, default: 5)
public int LeaseMinutes { get; set; } = 5;
}
///
/// Request model for updating an existing presence activity.
/// All fields are optional and will only update if provided.
///
public class UpdateActivityRequest
{
/// The type of presence activity (optional update)
public PresenceType? Type { get; set; }
/// User-defined identifier update
public string? ManualId { get; set; }
/// Title update
public string? Title { get; set; }
/// Subtitle update
public string? Subtitle { get; set; }
/// Caption update
public string? Caption { get; set; }
/// Metadata update
public Dictionary? Meta { get; set; }
/// Lease renewal in minutes
public int? LeaseMinutes { get; set; }
}
}