🎨 Split the account current related endpoints
This commit is contained in:
parent
6a426efde9
commit
7f4c756365
@ -120,123 +120,6 @@ public class AccountController(
|
|||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpGet("me")]
|
|
||||||
[ProducesResponseType<Account>(StatusCodes.Status200OK)]
|
|
||||||
public async Task<ActionResult<Account>> GetCurrentIdentity()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
var userId = currentUser.Id;
|
|
||||||
|
|
||||||
var account = await db.Accounts
|
|
||||||
.Include(e => e.Badges)
|
|
||||||
.Include(e => e.Profile)
|
|
||||||
.Where(e => e.Id == userId)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
return Ok(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BasicInfoRequest
|
|
||||||
{
|
|
||||||
[MaxLength(256)] public string? Nick { get; set; }
|
|
||||||
[MaxLength(32)] public string? Language { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpPatch("me")]
|
|
||||||
public async Task<ActionResult<Account>> UpdateBasicInfo([FromBody] BasicInfoRequest request)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var account = await db.Accounts.FirstAsync(a => a.Id == currentUser.Id);
|
|
||||||
|
|
||||||
if (request.Nick is not null) account.Nick = request.Nick;
|
|
||||||
if (request.Language is not null) account.Language = request.Language;
|
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
await accounts.PurgeAccountCache(currentUser);
|
|
||||||
return currentUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProfileRequest
|
|
||||||
{
|
|
||||||
[MaxLength(256)] public string? FirstName { get; set; }
|
|
||||||
[MaxLength(256)] public string? MiddleName { get; set; }
|
|
||||||
[MaxLength(256)] public string? LastName { get; set; }
|
|
||||||
[MaxLength(4096)] public string? Bio { get; set; }
|
|
||||||
|
|
||||||
[MaxLength(32)] public string? PictureId { get; set; }
|
|
||||||
[MaxLength(32)] public string? BackgroundId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpPatch("me/profile")]
|
|
||||||
public async Task<ActionResult<Profile>> UpdateProfile([FromBody] ProfileRequest request)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
var userId = currentUser.Id;
|
|
||||||
|
|
||||||
var profile = await db.AccountProfiles
|
|
||||||
.Where(p => p.Account.Id == userId)
|
|
||||||
.Include(profile => profile.Background)
|
|
||||||
.Include(profile => profile.Picture)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (profile is null) return BadRequest("Unable to get your account.");
|
|
||||||
|
|
||||||
if (request.FirstName is not null) profile.FirstName = request.FirstName;
|
|
||||||
if (request.MiddleName is not null) profile.MiddleName = request.MiddleName;
|
|
||||||
if (request.LastName is not null) profile.LastName = request.LastName;
|
|
||||||
if (request.Bio is not null) profile.Bio = request.Bio;
|
|
||||||
|
|
||||||
if (request.PictureId is not null)
|
|
||||||
{
|
|
||||||
var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync();
|
|
||||||
if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud.");
|
|
||||||
if (profile.Picture is not null)
|
|
||||||
await fs.MarkUsageAsync(profile.Picture, -1);
|
|
||||||
|
|
||||||
profile.Picture = picture;
|
|
||||||
await fs.MarkUsageAsync(picture, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.BackgroundId is not null)
|
|
||||||
{
|
|
||||||
var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync();
|
|
||||||
if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud.");
|
|
||||||
if (profile.Background is not null)
|
|
||||||
await fs.MarkUsageAsync(profile.Background, -1);
|
|
||||||
|
|
||||||
profile.Background = background;
|
|
||||||
await fs.MarkUsageAsync(background, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.Update(profile);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
await accounts.PurgeAccountCache(currentUser);
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("me")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult> RequestDeleteAccount()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await accounts.RequestAccountDeletion(currentUser);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
return BadRequest("You already requested account deletion within 24 hours.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RecoveryPasswordRequest
|
public class RecoveryPasswordRequest
|
||||||
{
|
{
|
||||||
[Required] public string Account { get; set; } = null!;
|
[Required] public string Account { get; set; } = null!;
|
||||||
@ -272,15 +155,6 @@ public class AccountController(
|
|||||||
public Instant? ClearedAt { get; set; }
|
public Instant? ClearedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/statuses")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<Status>> GetCurrentStatus()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
var status = await events.GetStatus(currentUser.Id);
|
|
||||||
return Ok(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{name}/statuses")]
|
[HttpGet("{name}/statuses")]
|
||||||
public async Task<ActionResult<Status>> GetOtherStatus(string name)
|
public async Task<ActionResult<Status>> GetOtherStatus(string name)
|
||||||
{
|
{
|
||||||
@ -291,138 +165,6 @@ public class AccountController(
|
|||||||
return Ok(status);
|
return Ok(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("me/statuses")]
|
|
||||||
[Authorize]
|
|
||||||
[RequiredPermission("global", "accounts.statuses.update")]
|
|
||||||
public async Task<ActionResult<Status>> UpdateStatus([FromBody] StatusRequest request)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
|
||||||
var status = await db.AccountStatuses
|
|
||||||
.Where(e => e.AccountId == currentUser.Id)
|
|
||||||
.Where(e => e.ClearedAt == null || e.ClearedAt > now)
|
|
||||||
.OrderByDescending(e => e.CreatedAt)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (status is null) return NotFound();
|
|
||||||
|
|
||||||
status.Attitude = request.Attitude;
|
|
||||||
status.IsInvisible = request.IsInvisible;
|
|
||||||
status.IsNotDisturb = request.IsNotDisturb;
|
|
||||||
status.Label = request.Label;
|
|
||||||
status.ClearedAt = request.ClearedAt;
|
|
||||||
|
|
||||||
db.Update(status);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
events.PurgeStatusCache(currentUser.Id);
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("me/statuses")]
|
|
||||||
[Authorize]
|
|
||||||
[RequiredPermission("global", "accounts.statuses.create")]
|
|
||||||
public async Task<ActionResult<Status>> CreateStatus([FromBody] StatusRequest request)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var status = new Status
|
|
||||||
{
|
|
||||||
AccountId = currentUser.Id,
|
|
||||||
Attitude = request.Attitude,
|
|
||||||
IsInvisible = request.IsInvisible,
|
|
||||||
IsNotDisturb = request.IsNotDisturb,
|
|
||||||
Label = request.Label,
|
|
||||||
ClearedAt = request.ClearedAt
|
|
||||||
};
|
|
||||||
|
|
||||||
return await events.CreateStatus(currentUser, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("me/statuses")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult> DeleteStatus()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
|
||||||
var status = await db.AccountStatuses
|
|
||||||
.Where(s => s.AccountId == currentUser.Id)
|
|
||||||
.Where(s => s.ClearedAt == null || s.ClearedAt > now)
|
|
||||||
.OrderByDescending(s => s.CreatedAt)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (status is null) return NotFound();
|
|
||||||
|
|
||||||
await events.ClearStatus(currentUser, status);
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("me/check-in")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<CheckInResult>> GetCheckInResult()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
var userId = currentUser.Id;
|
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
|
||||||
var today = now.InUtc().Date;
|
|
||||||
var startOfDay = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
|
||||||
var endOfDay = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
|
||||||
|
|
||||||
var result = await db.AccountCheckInResults
|
|
||||||
.Where(x => x.AccountId == userId)
|
|
||||||
.Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay)
|
|
||||||
.OrderByDescending(x => x.CreatedAt)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
return result is null ? NotFound() : Ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("me/check-in")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<CheckInResult>> DoCheckIn([FromBody] string? captchaToken)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var isAvailable = await events.CheckInDailyIsAvailable(currentUser);
|
|
||||||
if (!isAvailable)
|
|
||||||
return BadRequest("Check-in is not available for today.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var needsCaptcha = await events.CheckInDailyDoAskCaptcha(currentUser);
|
|
||||||
return needsCaptcha switch
|
|
||||||
{
|
|
||||||
true when string.IsNullOrWhiteSpace(captchaToken) => StatusCode(423,
|
|
||||||
"Captcha is required for this check-in."),
|
|
||||||
true when !await auth.ValidateCaptcha(captchaToken!) => BadRequest("Invalid captcha token."),
|
|
||||||
_ => await events.CheckInDaily(currentUser)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("me/calendar")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<List<DailyEventResponse>>> GetEventCalendar([FromQuery] int? month,
|
|
||||||
[FromQuery] int? year)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
|
||||||
month ??= currentDate.Month;
|
|
||||||
year ??= currentDate.Year;
|
|
||||||
|
|
||||||
if (month is < 1 or > 12) return BadRequest("Invalid month.");
|
|
||||||
if (year < 1) return BadRequest("Invalid year.");
|
|
||||||
|
|
||||||
var calendar = await events.GetEventCalendar(currentUser, month.Value, year.Value);
|
|
||||||
return Ok(calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{name}/calendar")]
|
[HttpGet("{name}/calendar")]
|
||||||
public async Task<ActionResult<List<DailyEventResponse>>> GetOtherEventCalendar(
|
public async Task<ActionResult<List<DailyEventResponse>>> GetOtherEventCalendar(
|
||||||
string name,
|
string name,
|
||||||
@ -444,30 +186,6 @@ public class AccountController(
|
|||||||
return Ok(calendar);
|
return Ok(calendar);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpGet("me/actions")]
|
|
||||||
[ProducesResponseType<List<ActionLog>>(StatusCodes.Status200OK)]
|
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
||||||
public async Task<ActionResult<List<ActionLog>>> GetActionLogs([FromQuery] int take = 20,
|
|
||||||
[FromQuery] int offset = 0)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var query = db.ActionLogs
|
|
||||||
.Where(log => log.AccountId == currentUser.Id)
|
|
||||||
.OrderByDescending(log => log.CreatedAt);
|
|
||||||
|
|
||||||
var total = await query.CountAsync();
|
|
||||||
Response.Headers.Append("X-Total", total.ToString());
|
|
||||||
|
|
||||||
var logs = await query
|
|
||||||
.Skip(offset)
|
|
||||||
.Take(take)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return Ok(logs);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("search")]
|
[HttpGet("search")]
|
||||||
public async Task<List<Account>> Search([FromQuery] string query, [FromQuery] int take = 20)
|
public async Task<List<Account>> Search([FromQuery] string query, [FromQuery] int take = 20)
|
||||||
{
|
{
|
||||||
|
294
DysonNetwork.Sphere/Account/AccountCurrentController.cs
Normal file
294
DysonNetwork.Sphere/Account/AccountCurrentController.cs
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using DysonNetwork.Sphere.Auth;
|
||||||
|
using DysonNetwork.Sphere.Permission;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
[Route("/accounts/me")]
|
||||||
|
public class AccountCurrentController(
|
||||||
|
AppDatabase db,
|
||||||
|
AccountService accounts,
|
||||||
|
FileService fs,
|
||||||
|
AccountEventService events,
|
||||||
|
AuthService auth
|
||||||
|
) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType<Account>(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<Account>> GetCurrentIdentity()
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var userId = currentUser.Id;
|
||||||
|
|
||||||
|
var account = await db.Accounts
|
||||||
|
.Include(e => e.Badges)
|
||||||
|
.Include(e => e.Profile)
|
||||||
|
.Where(e => e.Id == userId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return Ok(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BasicInfoRequest
|
||||||
|
{
|
||||||
|
[MaxLength(256)] public string? Nick { get; set; }
|
||||||
|
[MaxLength(32)] public string? Language { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch]
|
||||||
|
public async Task<ActionResult<Account>> UpdateBasicInfo([FromBody] BasicInfoRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var account = await db.Accounts.FirstAsync(a => a.Id == currentUser.Id);
|
||||||
|
|
||||||
|
if (request.Nick is not null) account.Nick = request.Nick;
|
||||||
|
if (request.Language is not null) account.Language = request.Language;
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
await accounts.PurgeAccountCache(currentUser);
|
||||||
|
return currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProfileRequest
|
||||||
|
{
|
||||||
|
[MaxLength(256)] public string? FirstName { get; set; }
|
||||||
|
[MaxLength(256)] public string? MiddleName { get; set; }
|
||||||
|
[MaxLength(256)] public string? LastName { get; set; }
|
||||||
|
[MaxLength(4096)] public string? Bio { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(32)] public string? PictureId { get; set; }
|
||||||
|
[MaxLength(32)] public string? BackgroundId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("profile")]
|
||||||
|
public async Task<ActionResult<Profile>> UpdateProfile([FromBody] ProfileRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var userId = currentUser.Id;
|
||||||
|
|
||||||
|
var profile = await db.AccountProfiles
|
||||||
|
.Where(p => p.Account.Id == userId)
|
||||||
|
.Include(profile => profile.Background)
|
||||||
|
.Include(profile => profile.Picture)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (profile is null) return BadRequest("Unable to get your account.");
|
||||||
|
|
||||||
|
if (request.FirstName is not null) profile.FirstName = request.FirstName;
|
||||||
|
if (request.MiddleName is not null) profile.MiddleName = request.MiddleName;
|
||||||
|
if (request.LastName is not null) profile.LastName = request.LastName;
|
||||||
|
if (request.Bio is not null) profile.Bio = request.Bio;
|
||||||
|
|
||||||
|
if (request.PictureId is not null)
|
||||||
|
{
|
||||||
|
var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync();
|
||||||
|
if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud.");
|
||||||
|
if (profile.Picture is not null)
|
||||||
|
await fs.MarkUsageAsync(profile.Picture, -1);
|
||||||
|
|
||||||
|
profile.Picture = picture;
|
||||||
|
await fs.MarkUsageAsync(picture, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.BackgroundId is not null)
|
||||||
|
{
|
||||||
|
var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync();
|
||||||
|
if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud.");
|
||||||
|
if (profile.Background is not null)
|
||||||
|
await fs.MarkUsageAsync(profile.Background, -1);
|
||||||
|
|
||||||
|
profile.Background = background;
|
||||||
|
await fs.MarkUsageAsync(background, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Update(profile);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await accounts.PurgeAccountCache(currentUser);
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete]
|
||||||
|
public async Task<ActionResult> RequestDeleteAccount()
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await accounts.RequestAccountDeletion(currentUser);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return BadRequest("You already requested account deletion within 24 hours.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("statuses")]
|
||||||
|
public async Task<ActionResult<Status>> GetCurrentStatus()
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var status = await events.GetStatus(currentUser.Id);
|
||||||
|
return Ok(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("statuses")]
|
||||||
|
[RequiredPermission("global", "accounts.statuses.update")]
|
||||||
|
public async Task<ActionResult<Status>> UpdateStatus([FromBody] AccountController.StatusRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var status = await db.AccountStatuses
|
||||||
|
.Where(e => e.AccountId == currentUser.Id)
|
||||||
|
.Where(e => e.ClearedAt == null || e.ClearedAt > now)
|
||||||
|
.OrderByDescending(e => e.CreatedAt)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (status is null) return NotFound();
|
||||||
|
|
||||||
|
status.Attitude = request.Attitude;
|
||||||
|
status.IsInvisible = request.IsInvisible;
|
||||||
|
status.IsNotDisturb = request.IsNotDisturb;
|
||||||
|
status.Label = request.Label;
|
||||||
|
status.ClearedAt = request.ClearedAt;
|
||||||
|
|
||||||
|
db.Update(status);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
events.PurgeStatusCache(currentUser.Id);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("statuses")]
|
||||||
|
[RequiredPermission("global", "accounts.statuses.create")]
|
||||||
|
public async Task<ActionResult<Status>> CreateStatus([FromBody] AccountController.StatusRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var status = new Status
|
||||||
|
{
|
||||||
|
AccountId = currentUser.Id,
|
||||||
|
Attitude = request.Attitude,
|
||||||
|
IsInvisible = request.IsInvisible,
|
||||||
|
IsNotDisturb = request.IsNotDisturb,
|
||||||
|
Label = request.Label,
|
||||||
|
ClearedAt = request.ClearedAt
|
||||||
|
};
|
||||||
|
|
||||||
|
return await events.CreateStatus(currentUser, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("me/statuses")]
|
||||||
|
public async Task<ActionResult> DeleteStatus()
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var status = await db.AccountStatuses
|
||||||
|
.Where(s => s.AccountId == currentUser.Id)
|
||||||
|
.Where(s => s.ClearedAt == null || s.ClearedAt > now)
|
||||||
|
.OrderByDescending(s => s.CreatedAt)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (status is null) return NotFound();
|
||||||
|
|
||||||
|
await events.ClearStatus(currentUser, status);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("check-in")]
|
||||||
|
public async Task<ActionResult<CheckInResult>> GetCheckInResult()
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var userId = currentUser.Id;
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var today = now.InUtc().Date;
|
||||||
|
var startOfDay = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||||
|
var endOfDay = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||||
|
|
||||||
|
var result = await db.AccountCheckInResults
|
||||||
|
.Where(x => x.AccountId == userId)
|
||||||
|
.Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return result is null ? NotFound() : Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("check-in")]
|
||||||
|
public async Task<ActionResult<CheckInResult>> DoCheckIn([FromBody] string? captchaToken)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var isAvailable = await events.CheckInDailyIsAvailable(currentUser);
|
||||||
|
if (!isAvailable)
|
||||||
|
return BadRequest("Check-in is not available for today.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var needsCaptcha = await events.CheckInDailyDoAskCaptcha(currentUser);
|
||||||
|
return needsCaptcha switch
|
||||||
|
{
|
||||||
|
true when string.IsNullOrWhiteSpace(captchaToken) => StatusCode(423,
|
||||||
|
"Captcha is required for this check-in."),
|
||||||
|
true when !await auth.ValidateCaptcha(captchaToken!) => BadRequest("Invalid captcha token."),
|
||||||
|
_ => await events.CheckInDaily(currentUser)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("calendar")]
|
||||||
|
public async Task<ActionResult<List<DailyEventResponse>>> GetEventCalendar([FromQuery] int? month,
|
||||||
|
[FromQuery] int? year)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
||||||
|
month ??= currentDate.Month;
|
||||||
|
year ??= currentDate.Year;
|
||||||
|
|
||||||
|
if (month is < 1 or > 12) return BadRequest("Invalid month.");
|
||||||
|
if (year < 1) return BadRequest("Invalid year.");
|
||||||
|
|
||||||
|
var calendar = await events.GetEventCalendar(currentUser, month.Value, year.Value);
|
||||||
|
return Ok(calendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("actions")]
|
||||||
|
[ProducesResponseType<List<ActionLog>>(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult<List<ActionLog>>> GetActionLogs(
|
||||||
|
[FromQuery] int take = 20,
|
||||||
|
[FromQuery] int offset = 0
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var query = db.ActionLogs
|
||||||
|
.Where(log => log.AccountId == currentUser.Id)
|
||||||
|
.OrderByDescending(log => log.CreatedAt);
|
||||||
|
|
||||||
|
var total = await query.CountAsync();
|
||||||
|
Response.Headers.Append("X-Total", total.ToString());
|
||||||
|
|
||||||
|
var logs = await query
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(take)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(logs);
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,8 @@ public class CloudFile : ModelBase
|
|||||||
/// Metrics
|
/// Metrics
|
||||||
/// When this used count keep zero, it means it's not used by anybody, so it can be recycled
|
/// When this used count keep zero, it means it's not used by anybody, so it can be recycled
|
||||||
public int UsedCount { get; set; } = 0;
|
public int UsedCount { get; set; } = 0;
|
||||||
|
/// An optional package identifier that indicates the cloud file's usage
|
||||||
|
[MaxLength(1024)] public string? Usage { get; set; }
|
||||||
|
|
||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user