using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using DorkNet.Models.Auth;
using DorkNet.Server.Auth;
using DorkNet.Server.Compat2018;
using DorkNet.Server.Services;
namespace DorkNet.Server.Controllers.API.Players.V2;
///
/// api/players/* for the June-2018 client. This branch is 2018-only, so every
/// endpoint returns the 2018 shape (keys Id/Developer/
/// JuniorProfile/… — NOT the 2020 PlayerProfile's AccountId/IsDeveloper).
///
/// The 2018 client mixes path versions: profile fetch + batch are v1, while
/// search + displayname are v2. We map both. Param names follow the 2018 client
/// (search ?name=, displayname form Name).
///
[ApiController]
[Authorize]
public class PlayersController(PlayerService playerService, NotificationService notificationService) : ControllerBase
{
private long CurrentPlayerId => this.RequireCurrentPlayerId();
// GET api/players/v1|v2/me
[HttpGet("/api/players/v1/me")]
[HttpGet("/api/players/v2/me")]
public async Task> GetSelf()
{
var player = await playerService.GetByIdAsync(CurrentPlayerId);
if (player is null) return NotFound();
return Ok(Player2018Mapper.From(player));
}
// GET api/players/v1|v2/{id} — primary profile fetch (2018 uses v1).
[HttpGet("/api/players/v1/{id:long}")]
[HttpGet("/api/players/v2/{id:long}")]
public async Task> GetById(long id)
{
var player = await playerService.GetByIdAsync(id);
if (player is null) return NotFound();
return Ok(Player2018Mapper.From(player));
}
// POST api/players/v1/list — batch profile resolution (JSON array of ids).
[HttpPost("/api/players/v1/list")]
[HttpPost("/api/players/v2/list")]
public async Task>> GetMany([FromBody] long[]? ids)
{
if (ids is null || ids.Length == 0) return Ok(new List());
var players = await playerService.GetByIdsAsync(ids);
return Ok(players.Select(Player2018Mapper.From).ToList());
}
// GET api/players/v2/search?name= (2018) — also accept ?query= for safety.
[HttpGet("/api/players/v1/search")]
[HttpGet("/api/players/v2/search")]
public async Task>> Search(
[FromQuery] string? name, [FromQuery] string? query)
{
var term = !string.IsNullOrWhiteSpace(name) ? name : query;
if (string.IsNullOrWhiteSpace(term)) return Ok(new List());
var results = await playerService.SearchAsync(term);
return Ok(results.Select(Player2018Mapper.From).ToList());
}
// POST api/players/v2/displayname (2018 form key "Name"). Also accept
// "DisplayName" for safety.
[HttpPost("/api/players/v1/displayname")]
[HttpPost("/api/players/v2/displayname")]
public async Task> UpdateDisplayName(
[FromForm] string? Name, [FromForm] string? DisplayName)
{
var newName = !string.IsNullOrWhiteSpace(Name) ? Name : DisplayName;
if (string.IsNullOrWhiteSpace(newName)) return BadRequest("Name is required");
if (!await playerService.UpdateDisplayNameAsync(CurrentPlayerId, newName)) return NotFound();
var player = await playerService.GetByIdAsync(CurrentPlayerId);
await notificationService.NotifyAsync(CurrentPlayerId,
Models.Notification.PushNotificationId.SubscriptionUpdateProfile, Player2018Mapper.From(player!));
return Ok(Player2018Mapper.From(player!));
}
// POST api/players/v1/bio (2018 form key "Bio").
[HttpPost("/api/players/v1/bio")]
[HttpPost("/api/players/v2/bio")]
public async Task> UpdateBio([FromForm] string? Bio)
{
await playerService.UpdateBioAsync(CurrentPlayerId, Bio ?? string.Empty);
var player = await playerService.GetByIdAsync(CurrentPlayerId);
return Ok(Player2018Mapper.From(player!));
}
// POST api/players/v1/avoidJuniors (form "AvoidJuniors"). Best-effort:
// acknowledged so the toggle in the watch settings doesn't 404. (Server
// doesn't currently filter matchmaking on this flag.)
[HttpPost("/api/players/v1/avoidJuniors")]
[HttpPut("/api/players/v1/avoidJuniors")]
public async Task> AvoidJuniors()
{
var player = await playerService.GetByIdAsync(CurrentPlayerId);
if (player is null) return NotFound();
return Ok(Player2018Mapper.From(player));
}
}