using DysonNetwork.Shared.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; 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")] 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). /// /// List of active presence activities [HttpGet("{identifier}")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> 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); } /// /// 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; } } }