Files
DorkNet/DorkNet.Server/Controllers/API/PlayerCheer/PlayerCheerController.cs
T
Alexa 579071858a feat: Add support for 2018 client API endpoints and data models
- Implemented new search endpoints for rooms to accommodate the 2018 client.
- Added support for retrieving room details and featured rooms in a format compatible with the 2018 client.
- Introduced new data models for login responses and player presence specific to the 2018 client.
- Created mappers to convert between internal entities and the 2018 wire formats for players and rooms.
- Updated existing storefront APIs to include versioned endpoints for the 2018 client.
- Enhanced the version controller to return dual-format responses for compatibility with both 2018 and 2020 clients.
- Refactored the configuration service to ensure trailing slashes in URLs for the 2018 client.
2026-06-07 15:32:52 +03:00

108 lines
4.5 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using DorkNet.Models.Notification;
using DorkNet.Server.Auth;
using DorkNet.Server.Data;
using DorkNet.Server.Data.Entities;
using DorkNet.Server.Services;
namespace DorkNet.Server.Controllers.API.PlayerCheer;
/// <summary>
/// api.{rec.net,localhost}/api/PlayerCheer/v1/* — peer cheer flow.
/// Cheers between players are stored as <see cref="CheerEntity"/>
/// rows keyed by <c>TargetPlayerId</c>. The "selected cheer" badge
/// each player can pin to their profile is stored as a
/// <see cref="PlayerSettingEntity"/> with key <c>SelectedCheer</c>.
/// </summary>
[ApiController]
[Authorize]
public class PlayerCheerController(
DorkNetDbContext db,
NotificationService notifications,
LevelService level) : ControllerBase
{
private long Me => this.RequireCurrentPlayerId();
/// <summary>POST <c>api/PlayerCheer/v1/create</c> — caller cheers
/// the target player. <c>Type</c> matches the same enum as
/// <c>CheerEntity.Type</c>. Idempotent on (caller, target, type)
/// to match <c>PlayerStateController.CheerPlayer</c>, so the same
/// category click doesn't inflate the count.</summary>
[HttpPost("api/PlayerCheer/v1/create")]
[Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")]
public async Task<IActionResult> Create(
[FromForm(Name = "TargetAccountId")] long? targetAccountId,
[FromForm(Name = "PlayerIdTo")] long? playerIdTo,
[FromForm(Name = "Type")] int type = 0,
[FromForm(Name = "CheerCategory")] int? cheerCategory = null)
{
// 2018 client posts PlayerIdTo + CheerCategory (RecNet.cs); 2020 posts
// TargetAccountId + Type. Accept both.
type = cheerCategory ?? type;
if ((targetAccountId ?? playerIdTo) is not long target || target <= 0)
return Ok(new { success = false, error = "missing_target" });
if (Me == target)
return Ok(new { success = false, error = "cannot_cheer_self" });
var existing = await db.Cheers.FirstOrDefaultAsync(c =>
c.FromPlayerId == Me && c.TargetPlayerId == target &&
c.TargetRoomId == 0 && c.Type == type);
if (existing is not null) return Ok(new { success = true, already_cheered = true });
db.Cheers.Add(new CheerEntity
{
FromPlayerId = Me,
TargetPlayerId = target,
Type = type,
});
await db.SaveChangesAsync();
await level.AwardXpAsync(target, LevelService.CheerReceivedXp, $"cheer_from:{Me}");
await notifications.NotifyAsync(target,
PushNotificationId.SubscriptionUpdateProfile,
new { Reason = "CheerReceived", From = Me, Type = type });
return Ok(new { success = true, error = "" });
}
/// <summary>POST <c>api/PlayerCheer/v1/SetSelectedCheer</c> — pin
/// a cheer category as the caller's selected badge. Stored on the
/// PlayerSetting key <c>SelectedCheer</c> (string-form int).</summary>
[HttpPost("api/PlayerCheer/v1/SetSelectedCheer")]
[Consumes("application/x-www-form-urlencoded", "multipart/form-data", "application/json")]
public async Task<IActionResult> SetSelectedCheer(
[FromForm(Name = "Cheer")] int cheer = 0,
[FromForm(Name = "CheerCategory")] int? cheerCategory = null)
{
cheer = cheerCategory ?? cheer; // 2018 sends CheerCategory
var existing = await db.PlayerSettings
.FirstOrDefaultAsync(s => s.PlayerId == Me && s.Key == "SelectedCheer");
if (existing is null)
db.PlayerSettings.Add(new PlayerSettingEntity { PlayerId = Me, Key = "SelectedCheer", Value = cheer.ToString() });
else
existing.Value = cheer.ToString();
await db.SaveChangesAsync();
return Ok(new { success = true, error = "" });
}
/// <summary>GET <c>api/PlayerCheer/v1/all</c> — every cheer the
/// caller has received. Drives the profile's "X cheers" badge.</summary>
[HttpGet("api/PlayerCheer/v1/all")]
public async Task<IActionResult> All()
{
var rows = await db.Cheers
.Where(c => c.TargetPlayerId == Me)
.OrderByDescending(c => c.CheeredAt)
.Take(200)
.Select(c => new
{
Id = c.Id,
FromPlayerId = c.FromPlayerId,
Type = c.Type,
CheeredAt = c.CheeredAt,
})
.ToListAsync();
return Ok(rows);
}
}