Compare commits
No commits in common. "aabe8269f52f55fbe71879f7b6a9ca8f7cf87520" and "d7d4fde06accf0b9e56878676760ec9faa0fb590" have entirely different histories.
aabe8269f5
...
d7d4fde06a
@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Extensions;
|
using NodaTime.Extensions;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Account;
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
@ -319,17 +318,17 @@ public class AccountController(
|
|||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var today = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
||||||
var today = now.InUtc().Date;
|
var localTime = new TimeOnly(0, 0);
|
||||||
var startOfDay = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
var startOfDay = today.ToDateOnly().ToDateTime(localTime).ToUniversalTime().ToInstant();
|
||||||
var endOfDay = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
var endOfDay = today.PlusDays(1).ToDateOnly().ToDateTime(localTime).ToUniversalTime().ToInstant();
|
||||||
|
|
||||||
var result = await db.AccountCheckInResults
|
var result = await db.AccountCheckInResults
|
||||||
.Where(x => x.AccountId == userId)
|
.Where(x => x.AccountId == userId)
|
||||||
.Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay)
|
.Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay)
|
||||||
.OrderByDescending(x => x.CreatedAt)
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
return result is null ? NotFound() : Ok(result);
|
return result is null ? NotFound() : Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,30 +390,7 @@ public class AccountController(
|
|||||||
var calendar = await events.GetEventCalendar(account, month.Value, year.Value, replaceInvisible: true);
|
var calendar = await events.GetEventCalendar(account, month.Value, year.Value, replaceInvisible: true);
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using DysonNetwork.Sphere.Activity;
|
using DysonNetwork.Sphere.Activity;
|
||||||
using DysonNetwork.Sphere.Connection;
|
using DysonNetwork.Sphere.Connection;
|
||||||
using DysonNetwork.Sphere.Wallet;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
@ -14,7 +13,6 @@ public class AccountEventService(
|
|||||||
ActivityService act,
|
ActivityService act,
|
||||||
WebSocketService ws,
|
WebSocketService ws,
|
||||||
IMemoryCache cache,
|
IMemoryCache cache,
|
||||||
PaymentService payment,
|
|
||||||
IStringLocalizer<Localization.AccountEventResource> localizer
|
IStringLocalizer<Localization.AccountEventResource> localizer
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -165,29 +163,12 @@ public class AccountEventService(
|
|||||||
{
|
{
|
||||||
Tips = tips,
|
Tips = tips,
|
||||||
Level = (CheckInResultLevel)Random.Next(Enum.GetValues<CheckInResultLevel>().Length),
|
Level = (CheckInResultLevel)Random.Next(Enum.GetValues<CheckInResultLevel>().Length),
|
||||||
AccountId = user.Id,
|
AccountId = user.Id
|
||||||
RewardExperience = 100,
|
|
||||||
RewardPoints = 10,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (result.RewardPoints.HasValue)
|
|
||||||
await payment.CreateTransactionWithAccountAsync(
|
|
||||||
null,
|
|
||||||
user.Id,
|
|
||||||
WalletCurrency.SourcePoint,
|
|
||||||
result.RewardPoints.Value,
|
|
||||||
$"Check-in reward on {now:yyyy/MM/dd}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
result.RewardPoints = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
db.AccountCheckInResults.Add(result);
|
db.AccountCheckInResults.Add(result);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
await act.CreateActivity(
|
await act.CreateActivity(
|
||||||
user,
|
user,
|
||||||
"accounts.check-in",
|
"accounts.check-in",
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Account;
|
|
||||||
|
|
||||||
public class ActionLogType
|
|
||||||
{
|
|
||||||
public const string NewLogin = "login";
|
|
||||||
public const string ChallengeAttempt = "challenges.attempt";
|
|
||||||
public const string ChallengeSuccess = "challenges.success";
|
|
||||||
public const string ChallengeFailure = "challenges.failure";
|
|
||||||
public const string PostCreate = "posts.create";
|
|
||||||
public const string PostUpdate = "posts.update";
|
|
||||||
public const string PostDelete = "posts.delete";
|
|
||||||
public const string PostReact = "posts.react";
|
|
||||||
public const string MessageCreate = "messages.create";
|
|
||||||
public const string MessageUpdate = "messages.update";
|
|
||||||
public const string MessageDelete = "messages.delete";
|
|
||||||
public const string MessageReact = "messages.react";
|
|
||||||
public const string PublisherCreate = "publishers.create";
|
|
||||||
public const string PublisherUpdate = "publishers.update";
|
|
||||||
public const string PublisherDelete = "publishers.delete";
|
|
||||||
public const string PublisherMemberInvite = "publishers.members.invite";
|
|
||||||
public const string PublisherMemberJoin = "publishers.members.join";
|
|
||||||
public const string PublisherMemberLeave = "publishers.members.leave";
|
|
||||||
public const string PublisherMemberKick = "publishers.members.kick";
|
|
||||||
public const string RealmCreate = "realms.create";
|
|
||||||
public const string RealmUpdate = "realms.update";
|
|
||||||
public const string RealmDelete = "realms.delete";
|
|
||||||
public const string RealmInvite = "realms.invite";
|
|
||||||
public const string RealmJoin = "realms.join";
|
|
||||||
public const string RealmLeave = "realms.leave";
|
|
||||||
public const string RealmKick = "realms.kick";
|
|
||||||
public const string ChatroomCreate = "chatrooms.create";
|
|
||||||
public const string ChatroomUpdate = "chatrooms.update";
|
|
||||||
public const string ChatroomDelete = "chatrooms.delete";
|
|
||||||
public const string ChatroomInvite = "chatrooms.invite";
|
|
||||||
public const string ChatroomJoin = "chatrooms.join";
|
|
||||||
public const string ChatroomLeave = "chatrooms.leave";
|
|
||||||
public const string ChatroomKick = "chatrooms.kick";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ActionLog : ModelBase
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
[MaxLength(4096)] public string Action { get; set; } = null!;
|
|
||||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
|
||||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
|
||||||
[MaxLength(128)] public string? IpAddress { get; set; }
|
|
||||||
public Point? Location { get; set; }
|
|
||||||
|
|
||||||
public Guid AccountId { get; set; }
|
|
||||||
public Account Account { get; set; } = null!;
|
|
||||||
public Guid? SessionId { get; set; }
|
|
||||||
public Auth.Session? Session { get; set; } = null!;
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
using Quartz;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using DysonNetwork.Sphere.Connection;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Account;
|
|
||||||
|
|
||||||
public class ActionLogService(AppDatabase db, GeoIpService geo) : IDisposable
|
|
||||||
{
|
|
||||||
private readonly ConcurrentQueue<ActionLog> _creationQueue = new();
|
|
||||||
|
|
||||||
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta)
|
|
||||||
{
|
|
||||||
var log = new ActionLog
|
|
||||||
{
|
|
||||||
Action = action,
|
|
||||||
AccountId = accountId,
|
|
||||||
Meta = meta,
|
|
||||||
};
|
|
||||||
|
|
||||||
_creationQueue.Enqueue(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CreateActionLogFromRequest(string action, Dictionary<string, object> meta, HttpRequest request)
|
|
||||||
{
|
|
||||||
if (request.HttpContext.Items["CurrentUser"] is not Account currentUser)
|
|
||||||
throw new ArgumentException("No user context was found");
|
|
||||||
if (request.HttpContext.Items["CurrentSession"] is not Auth.Session currentSession)
|
|
||||||
throw new ArgumentException("No session context was found");
|
|
||||||
|
|
||||||
var log = new ActionLog
|
|
||||||
{
|
|
||||||
Action = action,
|
|
||||||
AccountId = currentUser.Id,
|
|
||||||
SessionId = currentSession.Id,
|
|
||||||
Meta = meta,
|
|
||||||
UserAgent = request.Headers.UserAgent,
|
|
||||||
IpAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(),
|
|
||||||
Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString())
|
|
||||||
};
|
|
||||||
|
|
||||||
_creationQueue.Enqueue(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task FlushQueue()
|
|
||||||
{
|
|
||||||
var workingQueue = new List<ActionLog>();
|
|
||||||
while (_creationQueue.TryDequeue(out var log))
|
|
||||||
workingQueue.Add(log);
|
|
||||||
|
|
||||||
if (workingQueue.Count != 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await db.ActionLogs.AddRangeAsync(workingQueue);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
foreach (var log in workingQueue)
|
|
||||||
_creationQueue.Enqueue(log);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
FlushQueue().Wait();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ActionLogFlushJob(ActionLogService als) : IJob
|
|
||||||
{
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
await als.FlushQueue();
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,8 +39,6 @@ public class CheckInResult : ModelBase
|
|||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
public CheckInResultLevel Level { get; set; }
|
public CheckInResultLevel Level { get; set; }
|
||||||
public decimal? RewardPoints { get; set; }
|
|
||||||
public int? RewardExperience { get; set; }
|
|
||||||
[Column(TypeName = "jsonb")] public ICollection<FortuneTip> Tips { get; set; } = new List<FortuneTip>();
|
[Column(TypeName = "jsonb")] public ICollection<FortuneTip> Tips { get; set; } = new List<FortuneTip>();
|
||||||
|
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
|
@ -36,7 +36,6 @@ public class AppDatabase(
|
|||||||
public DbSet<Account.Notification> Notifications { get; set; }
|
public DbSet<Account.Notification> Notifications { get; set; }
|
||||||
public DbSet<Account.NotificationPushSubscription> NotificationPushSubscriptions { get; set; }
|
public DbSet<Account.NotificationPushSubscription> NotificationPushSubscriptions { get; set; }
|
||||||
public DbSet<Account.Badge> Badges { get; set; }
|
public DbSet<Account.Badge> Badges { get; set; }
|
||||||
public DbSet<Account.ActionLog> ActionLogs { get; set; }
|
|
||||||
|
|
||||||
public DbSet<Auth.Session> AuthSessions { get; set; }
|
public DbSet<Auth.Session> AuthSessions { get; set; }
|
||||||
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
|
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
|
||||||
@ -75,7 +74,6 @@ public class AppDatabase(
|
|||||||
public DbSet<Wallet.Transaction> PaymentTransactions { get; set; }
|
public DbSet<Wallet.Transaction> PaymentTransactions { get; set; }
|
||||||
|
|
||||||
public DbSet<Developer.CustomApp> CustomApps { get; set; }
|
public DbSet<Developer.CustomApp> CustomApps { get; set; }
|
||||||
public DbSet<Developer.CustomAppSecret> CustomAppSecrets { get; set; }
|
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
@ -86,10 +84,7 @@ public class AppDatabase(
|
|||||||
|
|
||||||
optionsBuilder.UseNpgsql(
|
optionsBuilder.UseNpgsql(
|
||||||
dataSource,
|
dataSource,
|
||||||
opt => opt
|
opt => opt.UseNodaTime().UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
|
||||||
.UseNodaTime()
|
|
||||||
.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
|
|
||||||
.UseNetTopologySuite()
|
|
||||||
).UseSnakeCaseNamingConvention();
|
).UseSnakeCaseNamingConvention();
|
||||||
|
|
||||||
optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>
|
optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>
|
||||||
|
@ -6,7 +6,6 @@ using NodaTime;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Sphere.Connection;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Auth;
|
namespace DysonNetwork.Sphere.Auth;
|
||||||
|
|
||||||
@ -16,15 +15,14 @@ public class AuthController(
|
|||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
AccountService accounts,
|
AccountService accounts,
|
||||||
AuthService auth,
|
AuthService auth,
|
||||||
GeoIpService geo,
|
IConfiguration configuration
|
||||||
ActionLogService als
|
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
public class ChallengeRequest
|
public class ChallengeRequest
|
||||||
{
|
{
|
||||||
[Required] public ChallengePlatform Platform { get; set; }
|
[Required] public ChallengePlatform Platform { get; set; }
|
||||||
[Required] [MaxLength(256)] public string Account { get; set; } = null!;
|
[Required] [MaxLength(256)] public string Account { get; set; } = string.Empty;
|
||||||
[Required] [MaxLength(512)] public string DeviceId { get; set; } = null!;
|
[Required] [MaxLength(512)] public string DeviceId { get; set; }
|
||||||
public List<string> Audiences { get; set; } = new();
|
public List<string> Audiences { get; set; } = new();
|
||||||
public List<string> Scopes { get; set; } = new();
|
public List<string> Scopes { get; set; } = new();
|
||||||
}
|
}
|
||||||
@ -59,22 +57,16 @@ public class AuthController(
|
|||||||
Scopes = request.Scopes,
|
Scopes = request.Scopes,
|
||||||
IpAddress = ipAddress,
|
IpAddress = ipAddress,
|
||||||
UserAgent = userAgent,
|
UserAgent = userAgent,
|
||||||
Location = geo.GetPointFromIp(ipAddress),
|
|
||||||
DeviceId = request.DeviceId,
|
DeviceId = request.DeviceId,
|
||||||
AccountId = account.Id
|
AccountId = account.Id
|
||||||
}.Normalize();
|
}.Normalize();
|
||||||
|
|
||||||
await db.AuthChallenges.AddAsync(challenge);
|
await db.AuthChallenges.AddAsync(challenge);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt,
|
|
||||||
new Dictionary<string, object> { { "challenge_id", challenge.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return challenge;
|
return challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("challenge/{id:guid}/factors")]
|
[HttpGet("challenge/{id}/factors")]
|
||||||
public async Task<ActionResult<List<AccountAuthFactor>>> GetChallengeFactors([FromRoute] Guid id)
|
public async Task<ActionResult<List<AccountAuthFactor>>> GetChallengeFactors([FromRoute] Guid id)
|
||||||
{
|
{
|
||||||
var challenge = await db.AuthChallenges
|
var challenge = await db.AuthChallenges
|
||||||
@ -86,7 +78,7 @@ public class AuthController(
|
|||||||
: challenge.Account.AuthFactors.ToList();
|
: challenge.Account.AuthFactors.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("challenge/{id:guid}/factors/{factorId:guid}")]
|
[HttpPost("challenge/{id}/factors/{factorId:guid}")]
|
||||||
public async Task<ActionResult> RequestFactorCode([FromRoute] Guid id, [FromRoute] Guid factorId)
|
public async Task<ActionResult> RequestFactorCode([FromRoute] Guid id, [FromRoute] Guid factorId)
|
||||||
{
|
{
|
||||||
var challenge = await db.AuthChallenges
|
var challenge = await db.AuthChallenges
|
||||||
@ -105,11 +97,11 @@ public class AuthController(
|
|||||||
|
|
||||||
public class PerformChallengeRequest
|
public class PerformChallengeRequest
|
||||||
{
|
{
|
||||||
[Required] public Guid FactorId { get; set; }
|
[Required] public long FactorId { get; set; }
|
||||||
[Required] public string Password { get; set; } = string.Empty;
|
[Required] public string Password { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("challenge/{id:guid}")]
|
[HttpPatch("challenge/{id}")]
|
||||||
public async Task<ActionResult<Challenge>> DoChallenge(
|
public async Task<ActionResult<Challenge>> DoChallenge(
|
||||||
[FromRoute] Guid id,
|
[FromRoute] Guid id,
|
||||||
[FromBody] PerformChallengeRequest request
|
[FromBody] PerformChallengeRequest request
|
||||||
@ -132,12 +124,6 @@ public class AuthController(
|
|||||||
challenge.StepRemain--;
|
challenge.StepRemain--;
|
||||||
challenge.BlacklistFactors.Add(factor.Id);
|
challenge.BlacklistFactors.Add(factor.Id);
|
||||||
db.Update(challenge);
|
db.Update(challenge);
|
||||||
als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess,
|
|
||||||
new Dictionary<string, object> {
|
|
||||||
{ "challenge_id", challenge.Id },
|
|
||||||
{ "factor_id", factor.Id }
|
|
||||||
}, Request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -148,26 +134,10 @@ public class AuthController(
|
|||||||
{
|
{
|
||||||
challenge.FailedAttempts++;
|
challenge.FailedAttempts++;
|
||||||
db.Update(challenge);
|
db.Update(challenge);
|
||||||
als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure,
|
|
||||||
new Dictionary<string, object> {
|
|
||||||
{ "challenge_id", challenge.Id },
|
|
||||||
{ "factor_id", factor.Id }
|
|
||||||
}, Request
|
|
||||||
);
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
return BadRequest("Invalid password.");
|
return BadRequest("Invalid password.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (challenge.StepRemain == 0)
|
|
||||||
{
|
|
||||||
als.CreateActionLogFromRequest(ActionLogType.NewLogin,
|
|
||||||
new Dictionary<string, object> {
|
|
||||||
{ "challenge_id", challenge.Id },
|
|
||||||
{ "account_id", challenge.AccountId }
|
|
||||||
}, Request
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
return challenge;
|
return challenge;
|
||||||
}
|
}
|
||||||
@ -240,6 +210,20 @@ public class AuthController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("test")]
|
||||||
|
public async Task<ActionResult> Test()
|
||||||
|
{
|
||||||
|
var sessionIdClaim = HttpContext.User.FindFirst("session_id")?.Value;
|
||||||
|
if (!Guid.TryParse(sessionIdClaim, out var sessionId))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var session = await db.AuthSessions.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||||
|
if (session is null) return NotFound();
|
||||||
|
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("captcha")]
|
[HttpPost("captcha")]
|
||||||
public async Task<ActionResult> ValidateCaptcha([FromBody] string token)
|
public async Task<ActionResult> ValidateCaptcha([FromBody] string token)
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Auth;
|
namespace DysonNetwork.Sphere.Auth;
|
||||||
|
|
||||||
@ -51,7 +50,6 @@ public class Challenge : ModelBase
|
|||||||
[MaxLength(512)] public string? UserAgent { get; set; }
|
[MaxLength(512)] public string? UserAgent { get; set; }
|
||||||
[MaxLength(256)] public string? DeviceId { get; set; }
|
[MaxLength(256)] public string? DeviceId { get; set; }
|
||||||
[MaxLength(1024)] public string? Nonce { get; set; }
|
[MaxLength(1024)] public string? Nonce { get; set; }
|
||||||
public Point? Location { get; set; }
|
|
||||||
|
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Account;
|
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
@ -11,12 +10,7 @@ namespace DysonNetwork.Sphere.Chat;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/chat")]
|
[Route("/chat")]
|
||||||
public class ChatRoomController(
|
public class ChatRoomController(AppDatabase db, FileService fs, ChatRoomService crs, RealmService rs) : ControllerBase
|
||||||
AppDatabase db,
|
|
||||||
FileService fs,
|
|
||||||
ChatRoomService crs,
|
|
||||||
RealmService rs,
|
|
||||||
ActionLogService als) : ControllerBase
|
|
||||||
{
|
{
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")]
|
||||||
public async Task<ActionResult<ChatRoom>> GetChatRoom(Guid id)
|
public async Task<ActionResult<ChatRoom>> GetChatRoom(Guid id)
|
||||||
@ -132,11 +126,6 @@ public class ChatRoomController(
|
|||||||
db.ChatRooms.Add(dmRoom);
|
db.ChatRooms.Add(dmRoom);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomCreate,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", dmRoom.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId);
|
var invitedMember = dmRoom.Members.First(m => m.AccountId == request.RelatedUserId);
|
||||||
await crs.SendInviteNotify(invitedMember);
|
await crs.SendInviteNotify(invitedMember);
|
||||||
|
|
||||||
@ -205,11 +194,6 @@ public class ChatRoomController(
|
|||||||
if (chatRoom.Background is not null)
|
if (chatRoom.Background is not null)
|
||||||
await fs.MarkUsageAsync(chatRoom.Background, 1);
|
await fs.MarkUsageAsync(chatRoom.Background, 1);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomCreate,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", chatRoom.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(chatRoom);
|
return Ok(chatRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,11 +255,6 @@ public class ChatRoomController(
|
|||||||
db.ChatRooms.Update(chatRoom);
|
db.ChatRooms.Update(chatRoom);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomUpdate,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", chatRoom.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(chatRoom);
|
return Ok(chatRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,11 +286,6 @@ public class ChatRoomController(
|
|||||||
if (chatRoom.Background is not null)
|
if (chatRoom.Background is not null)
|
||||||
await fs.MarkUsageAsync(chatRoom.Background, -1);
|
await fs.MarkUsageAsync(chatRoom.Background, -1);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomDelete,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", chatRoom.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,11 +401,6 @@ public class ChatRoomController(
|
|||||||
|
|
||||||
await crs.SendInviteNotify(newMember);
|
await crs.SendInviteNotify(newMember);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomInvite,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", chatRoom.Id }, { "account_id", relatedUser.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(newMember);
|
return Ok(newMember);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,11 +440,6 @@ public class ChatRoomController(
|
|||||||
db.Update(member);
|
db.Update(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomJoin,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", roomId } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(member);
|
return Ok(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,11 +559,6 @@ public class ChatRoomController(
|
|||||||
db.ChatMembers.Remove(targetMember);
|
db.ChatMembers.Remove(targetMember);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomKick,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", roomId }, { "account_id", memberId } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,11 +593,6 @@ public class ChatRoomController(
|
|||||||
db.ChatMembers.Remove(member);
|
db.ChatMembers.Remove(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.ChatroomLeave,
|
|
||||||
new Dictionary<string, object> { { "chatroom_id", roomId } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,56 +0,0 @@
|
|||||||
using MaxMind.GeoIP2;
|
|
||||||
using NetTopologySuite.Geometries;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Connection;
|
|
||||||
|
|
||||||
public class GeoIpOptions
|
|
||||||
{
|
|
||||||
public string DatabasePath { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GeoIpService(IOptions<GeoIpOptions> options)
|
|
||||||
{
|
|
||||||
private readonly string _databasePath = options.Value.DatabasePath;
|
|
||||||
private readonly GeometryFactory _geometryFactory = new(new PrecisionModel(), 4326); // 4326 is the SRID for WGS84
|
|
||||||
|
|
||||||
public Point? GetPointFromIp(string? ipAddress)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ipAddress))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var reader = new DatabaseReader(_databasePath);
|
|
||||||
var city = reader.City(ipAddress);
|
|
||||||
|
|
||||||
if (city?.Location == null || !city.Location.HasCoordinates)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return _geometryFactory.CreatePoint(new Coordinate(
|
|
||||||
city.Location.Longitude ?? 0,
|
|
||||||
city.Location.Latitude ?? 0));
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MaxMind.GeoIP2.Responses.CityResponse? GetFromIp(string? ipAddress)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ipAddress))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var reader = new DatabaseReader(_databasePath);
|
|
||||||
return reader.City(ipAddress);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Developer;
|
namespace DysonNetwork.Sphere.Developer;
|
||||||
@ -20,20 +19,7 @@ public class CustomApp : ModelBase
|
|||||||
public CustomAppStatus Status { get; set; } = CustomAppStatus.Developing;
|
public CustomAppStatus Status { get; set; } = CustomAppStatus.Developing;
|
||||||
public Instant? VerifiedAt { get; set; }
|
public Instant? VerifiedAt { get; set; }
|
||||||
[MaxLength(4096)] public string? VerifiedAs { get; set; }
|
[MaxLength(4096)] public string? VerifiedAs { get; set; }
|
||||||
|
|
||||||
[JsonIgnore] private ICollection<CustomAppSecret> Secrets { get; set; } = new List<CustomAppSecret>();
|
|
||||||
|
|
||||||
public Guid PublisherId { get; set; }
|
public Guid PublisherId { get; set; }
|
||||||
public Publisher.Publisher Developer { get; set; } = null!;
|
public Publisher.Publisher Developer { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CustomAppSecret : ModelBase
|
|
||||||
{
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
[MaxLength(1024)] public string Secret { get; set; } = null!;
|
|
||||||
[MaxLength(4096)] public string? Remarks { get; set; } = null!;
|
|
||||||
public Instant? ExpiredAt { get; set; }
|
|
||||||
|
|
||||||
public Guid AppId { get; set; }
|
|
||||||
public CustomApp App { get; set; } = null!;
|
|
||||||
}
|
|
@ -15,7 +15,6 @@
|
|||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="MailKit" Version="4.11.0" />
|
<PackageReference Include="MailKit" Version="4.11.0" />
|
||||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
||||||
@ -36,7 +35,6 @@
|
|||||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
||||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||||
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
<PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,82 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using NodaTime;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddCustomAppsAndSecrets : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<Guid>(
|
|
||||||
name: "issuer_app_id",
|
|
||||||
table: "payment_orders",
|
|
||||||
type: "uuid",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "custom_app_secrets",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
|
||||||
secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
|
||||||
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
|
||||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
|
||||||
app_id = table.Column<Guid>(type: "uuid", nullable: false),
|
|
||||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
|
||||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
|
||||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("pk_custom_app_secrets", x => x.id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "fk_custom_app_secrets_custom_apps_app_id",
|
|
||||||
column: x => x.app_id,
|
|
||||||
principalTable: "custom_apps",
|
|
||||||
principalColumn: "id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_payment_orders_issuer_app_id",
|
|
||||||
table: "payment_orders",
|
|
||||||
column: "issuer_app_id");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_custom_app_secrets_app_id",
|
|
||||||
table: "custom_app_secrets",
|
|
||||||
column: "app_id");
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "fk_payment_orders_custom_apps_issuer_app_id",
|
|
||||||
table: "payment_orders",
|
|
||||||
column: "issuer_app_id",
|
|
||||||
principalTable: "custom_apps",
|
|
||||||
principalColumn: "id");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "fk_payment_orders_custom_apps_issuer_app_id",
|
|
||||||
table: "payment_orders");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "custom_app_secrets");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "ix_payment_orders_issuer_app_id",
|
|
||||||
table: "payment_orders");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "issuer_app_id",
|
|
||||||
table: "payment_orders");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddRewardToCheckIn : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<int>(
|
|
||||||
name: "reward_experience",
|
|
||||||
table: "account_check_in_results",
|
|
||||||
type: "integer",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<decimal>(
|
|
||||||
name: "reward_points",
|
|
||||||
table: "account_check_in_results",
|
|
||||||
type: "numeric",
|
|
||||||
nullable: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "reward_experience",
|
|
||||||
table: "account_check_in_results");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "reward_points",
|
|
||||||
table: "account_check_in_results");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,84 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using NetTopologySuite.Geometries;
|
|
||||||
using NodaTime;
|
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddActionLogs : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AlterDatabase()
|
|
||||||
.Annotation("Npgsql:PostgresExtension:postgis", ",,");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<Point>(
|
|
||||||
name: "location",
|
|
||||||
table: "auth_challenges",
|
|
||||||
type: "geometry",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "action_logs",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
|
||||||
action = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
|
||||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
|
|
||||||
user_agent = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
|
|
||||||
ip_address = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
|
||||||
location = table.Column<Point>(type: "geometry", nullable: true),
|
|
||||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
|
||||||
session_id = table.Column<Guid>(type: "uuid", nullable: true),
|
|
||||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
|
||||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
|
||||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("pk_action_logs", x => x.id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "fk_action_logs_accounts_account_id",
|
|
||||||
column: x => x.account_id,
|
|
||||||
principalTable: "accounts",
|
|
||||||
principalColumn: "id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "fk_action_logs_auth_sessions_session_id",
|
|
||||||
column: x => x.session_id,
|
|
||||||
principalTable: "auth_sessions",
|
|
||||||
principalColumn: "id");
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_action_logs_account_id",
|
|
||||||
table: "action_logs",
|
|
||||||
column: "account_id");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_action_logs_session_id",
|
|
||||||
table: "action_logs",
|
|
||||||
column: "session_id");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "action_logs");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "location",
|
|
||||||
table: "auth_challenges");
|
|
||||||
|
|
||||||
migrationBuilder.AlterDatabase()
|
|
||||||
.OldAnnotation("Npgsql:PostgresExtension:postgis", ",,");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,11 +7,9 @@ using DysonNetwork.Sphere.Account;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using NetTopologySuite.Geometries;
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
using NpgsqlTypes;
|
using NpgsqlTypes;
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
@ -27,7 +25,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasAnnotation("ProductVersion", "9.0.3")
|
.HasAnnotation("ProductVersion", "9.0.3")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
|
||||||
@ -172,70 +169,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("account_contacts", (string)null);
|
b.ToTable("account_contacts", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
b.Property<Guid>("AccountId")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("account_id");
|
|
||||||
|
|
||||||
b.Property<string>("Action")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(4096)
|
|
||||||
.HasColumnType("character varying(4096)")
|
|
||||||
.HasColumnName("action");
|
|
||||||
|
|
||||||
b.Property<Instant>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("created_at");
|
|
||||||
|
|
||||||
b.Property<Instant?>("DeletedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("deleted_at");
|
|
||||||
|
|
||||||
b.Property<string>("IpAddress")
|
|
||||||
.HasMaxLength(128)
|
|
||||||
.HasColumnType("character varying(128)")
|
|
||||||
.HasColumnName("ip_address");
|
|
||||||
|
|
||||||
b.Property<Point>("Location")
|
|
||||||
.HasColumnType("geometry")
|
|
||||||
.HasColumnName("location");
|
|
||||||
|
|
||||||
b.Property<Dictionary<string, object>>("Meta")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("jsonb")
|
|
||||||
.HasColumnName("meta");
|
|
||||||
|
|
||||||
b.Property<Guid?>("SessionId")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("session_id");
|
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("updated_at");
|
|
||||||
|
|
||||||
b.Property<string>("UserAgent")
|
|
||||||
.HasMaxLength(512)
|
|
||||||
.HasColumnType("character varying(512)")
|
|
||||||
.HasColumnName("user_agent");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("pk_action_logs");
|
|
||||||
|
|
||||||
b.HasIndex("AccountId")
|
|
||||||
.HasDatabaseName("ix_action_logs_account_id");
|
|
||||||
|
|
||||||
b.HasIndex("SessionId")
|
|
||||||
.HasDatabaseName("ix_action_logs_session_id");
|
|
||||||
|
|
||||||
b.ToTable("action_logs", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -316,14 +249,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("level");
|
.HasColumnName("level");
|
||||||
|
|
||||||
b.Property<int?>("RewardExperience")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("reward_experience");
|
|
||||||
|
|
||||||
b.Property<decimal?>("RewardPoints")
|
|
||||||
.HasColumnType("numeric")
|
|
||||||
.HasColumnName("reward_points");
|
|
||||||
|
|
||||||
b.Property<ICollection<FortuneTip>>("Tips")
|
b.Property<ICollection<FortuneTip>>("Tips")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
@ -781,10 +706,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("character varying(128)")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("ip_address");
|
.HasColumnName("ip_address");
|
||||||
|
|
||||||
b.Property<Point>("Location")
|
|
||||||
.HasColumnType("geometry")
|
|
||||||
.HasColumnName("location");
|
|
||||||
|
|
||||||
b.Property<string>("Nonce")
|
b.Property<string>("Nonce")
|
||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
.HasColumnType("character varying(1024)")
|
.HasColumnType("character varying(1024)")
|
||||||
@ -1276,53 +1197,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("custom_apps", (string)null);
|
b.ToTable("custom_apps", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b =>
|
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
b.Property<Guid>("AppId")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("app_id");
|
|
||||||
|
|
||||||
b.Property<Instant>("CreatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("created_at");
|
|
||||||
|
|
||||||
b.Property<Instant?>("DeletedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("deleted_at");
|
|
||||||
|
|
||||||
b.Property<Instant?>("ExpiredAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("expired_at");
|
|
||||||
|
|
||||||
b.Property<string>("Remarks")
|
|
||||||
.HasMaxLength(4096)
|
|
||||||
.HasColumnType("character varying(4096)")
|
|
||||||
.HasColumnName("remarks");
|
|
||||||
|
|
||||||
b.Property<string>("Secret")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(1024)
|
|
||||||
.HasColumnType("character varying(1024)")
|
|
||||||
.HasColumnName("secret");
|
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
|
||||||
.HasColumnType("timestamp with time zone")
|
|
||||||
.HasColumnName("updated_at");
|
|
||||||
|
|
||||||
b.HasKey("Id")
|
|
||||||
.HasName("pk_custom_app_secrets");
|
|
||||||
|
|
||||||
b.HasIndex("AppId")
|
|
||||||
.HasDatabaseName("ix_custom_app_secrets_app_id");
|
|
||||||
|
|
||||||
b.ToTable("custom_app_secrets", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -2303,10 +2177,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("expired_at");
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
b.Property<Guid?>("IssuerAppId")
|
|
||||||
.HasColumnType("uuid")
|
|
||||||
.HasColumnName("issuer_app_id");
|
|
||||||
|
|
||||||
b.Property<Guid>("PayeeWalletId")
|
b.Property<Guid>("PayeeWalletId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("payee_wallet_id");
|
.HasColumnName("payee_wallet_id");
|
||||||
@ -2331,9 +2201,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_payment_orders");
|
.HasName("pk_payment_orders");
|
||||||
|
|
||||||
b.HasIndex("IssuerAppId")
|
|
||||||
.HasDatabaseName("ix_payment_orders_issuer_app_id");
|
|
||||||
|
|
||||||
b.HasIndex("PayeeWalletId")
|
b.HasIndex("PayeeWalletId")
|
||||||
.HasDatabaseName("ix_payment_orders_payee_wallet_id");
|
.HasDatabaseName("ix_payment_orders_payee_wallet_id");
|
||||||
|
|
||||||
@ -2556,25 +2423,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Account");
|
b.Navigation("Account");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.ActionLog", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AccountId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired()
|
|
||||||
.HasConstraintName("fk_action_logs_accounts_account_id");
|
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Auth.Session", "Session")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("SessionId")
|
|
||||||
.HasConstraintName("fk_action_logs_auth_sessions_session_id");
|
|
||||||
|
|
||||||
b.Navigation("Account");
|
|
||||||
|
|
||||||
b.Navigation("Session");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Account.Badge", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
@ -2894,18 +2742,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Developer");
|
b.Navigation("Developer");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("AppId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired()
|
|
||||||
.HasConstraintName("fk_custom_app_secrets_custom_apps_app_id");
|
|
||||||
|
|
||||||
b.Navigation("App");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
||||||
@ -3185,11 +3021,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("IssuerAppId")
|
|
||||||
.HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id");
|
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet")
|
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PayeeWalletId")
|
.HasForeignKey("PayeeWalletId")
|
||||||
@ -3202,8 +3033,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasForeignKey("TransactionId")
|
.HasForeignKey("TransactionId")
|
||||||
.HasConstraintName("fk_payment_orders_payment_transactions_transaction_id");
|
.HasConstraintName("fk_payment_orders_payment_transactions_transaction_id");
|
||||||
|
|
||||||
b.Navigation("IssuerApp");
|
|
||||||
|
|
||||||
b.Navigation("PayeeWallet");
|
b.Navigation("PayeeWallet");
|
||||||
|
|
||||||
b.Navigation("Transaction");
|
b.Navigation("Transaction");
|
||||||
|
@ -17,8 +17,7 @@ public class PostController(
|
|||||||
PostService ps,
|
PostService ps,
|
||||||
PublisherService pub,
|
PublisherService pub,
|
||||||
RelationshipService rels,
|
RelationshipService rels,
|
||||||
IServiceScopeFactory factory,
|
IServiceScopeFactory factory
|
||||||
ActionLogService als
|
|
||||||
)
|
)
|
||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
@ -228,11 +227,6 @@ public class PostController(
|
|||||||
await subs.NotifySubscribersPostAsync(post);
|
await subs.NotifySubscribersPostAsync(post);
|
||||||
});
|
});
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PostCreate,
|
|
||||||
new Dictionary<string, object> { { "post_id", post.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,12 +268,6 @@ public class PostController(
|
|||||||
var isRemoving = await ps.ModifyPostVotes(post, reaction, isExistingReaction, isSelfReact);
|
var isRemoving = await ps.ModifyPostVotes(post, reaction, isExistingReaction, isSelfReact);
|
||||||
|
|
||||||
if (isRemoving) return NoContent();
|
if (isRemoving) return NoContent();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PostReact,
|
|
||||||
new Dictionary<string, object> { { "post_id", post.Id }, { "reaction", request.Symbol } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(reaction);
|
return Ok(reaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,11 +312,6 @@ public class PostController(
|
|||||||
return BadRequest(err.Message);
|
return BadRequest(err.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PostUpdate,
|
|
||||||
new Dictionary<string, object> { { "post_id", post.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(post);
|
return Ok(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,12 +331,6 @@ public class PostController(
|
|||||||
return StatusCode(403, "You need at least be an editor to delete the publisher's post.");
|
return StatusCode(403, "You need at least be an editor to delete the publisher's post.");
|
||||||
|
|
||||||
await ps.DeletePostAsync(post);
|
await ps.DeletePostAsync(post);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PostDelete,
|
|
||||||
new Dictionary<string, object> { { "post_id", post.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -148,14 +148,11 @@ builder.Services.AddSingleton(tusDiskStore);
|
|||||||
builder.Services.AddScoped<IWebSocketPacketHandler, MessageReadHandler>();
|
builder.Services.AddScoped<IWebSocketPacketHandler, MessageReadHandler>();
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
builder.Services.Configure<GeoIpOptions>(builder.Configuration.GetSection("GeoIP"));
|
|
||||||
builder.Services.AddScoped<GeoIpService>();
|
|
||||||
builder.Services.AddScoped<WebSocketService>();
|
builder.Services.AddScoped<WebSocketService>();
|
||||||
builder.Services.AddScoped<EmailService>();
|
builder.Services.AddScoped<EmailService>();
|
||||||
builder.Services.AddScoped<PermissionService>();
|
builder.Services.AddScoped<PermissionService>();
|
||||||
builder.Services.AddScoped<AccountService>();
|
builder.Services.AddScoped<AccountService>();
|
||||||
builder.Services.AddScoped<AccountEventService>();
|
builder.Services.AddScoped<AccountEventService>();
|
||||||
builder.Services.AddSingleton<ActionLogService>();
|
|
||||||
builder.Services.AddScoped<RelationshipService>();
|
builder.Services.AddScoped<RelationshipService>();
|
||||||
builder.Services.AddScoped<MagicSpellService>();
|
builder.Services.AddScoped<MagicSpellService>();
|
||||||
builder.Services.AddScoped<NotificationService>();
|
builder.Services.AddScoped<NotificationService>();
|
||||||
@ -191,16 +188,6 @@ builder.Services.AddQuartz(q =>
|
|||||||
.WithIdentity("CloudFilesUnusedRecyclingTrigger")
|
.WithIdentity("CloudFilesUnusedRecyclingTrigger")
|
||||||
.WithSimpleSchedule(o => o.WithIntervalInHours(1).RepeatForever())
|
.WithSimpleSchedule(o => o.WithIntervalInHours(1).RepeatForever())
|
||||||
);
|
);
|
||||||
|
|
||||||
var actionLogFlushJob = new JobKey("ActionLogFlush");
|
|
||||||
q.AddJob<ActionLogFlushJob>(opts => opts.WithIdentity(actionLogFlushJob));
|
|
||||||
q.AddTrigger(opts => opts
|
|
||||||
.ForJob(actionLogFlushJob)
|
|
||||||
.WithIdentity("ActionLogFlushTrigger")
|
|
||||||
.WithSimpleSchedule(o => o
|
|
||||||
.WithIntervalInMinutes(5)
|
|
||||||
.RepeatForever())
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Account;
|
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
@ -13,11 +12,11 @@ namespace DysonNetwork.Sphere.Publisher;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/publishers")]
|
[Route("/publishers")]
|
||||||
public class PublisherController(AppDatabase db, PublisherService ps, FileService fs, ActionLogService als)
|
public class PublisherController(AppDatabase db, PublisherService ps, FileService fs)
|
||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("{name}")]
|
[HttpGet("{name}")]
|
||||||
public async Task<ActionResult<Publisher>> GetPublisher(string name)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> GetPublisher(string name)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<List<Publisher>>> ListManagedPublishers()
|
public async Task<ActionResult<List<Sphere.Publisher.Publisher>>> ListManagedPublishers()
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -97,7 +96,15 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (publisher is null) return NotFound();
|
if (publisher is null) return NotFound();
|
||||||
|
|
||||||
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, request.Role))
|
var member = await db.PublisherMembers
|
||||||
|
.Where(m => m.AccountId == userId)
|
||||||
|
.Where(m => m.PublisherId == publisher.Id)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (member is null) return StatusCode(403, "You are not even a member of the targeted publisher.");
|
||||||
|
if (member.Role < PublisherMemberRole.Manager)
|
||||||
|
return StatusCode(403,
|
||||||
|
"You need at least be a manager to invite other members to collaborate this publisher.");
|
||||||
|
if (member.Role < request.Role)
|
||||||
return StatusCode(403, "You cannot invite member has higher permission than yours.");
|
return StatusCode(403, "You cannot invite member has higher permission than yours.");
|
||||||
|
|
||||||
var newMember = new PublisherMember
|
var newMember = new PublisherMember
|
||||||
@ -110,21 +117,12 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
db.PublisherMembers.Add(newMember);
|
db.PublisherMembers.Add(newMember);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherMemberInvite,
|
|
||||||
new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "publisher_id", publisher.Id },
|
|
||||||
{ "account_id", relatedUser.Id }
|
|
||||||
}, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(newMember);
|
return Ok(newMember);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("invites/{name}/accept")]
|
[HttpPost("invites/{name}/accept")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<Publisher>> AcceptMemberInvite(string name)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> AcceptMemberInvite(string name)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -140,11 +138,6 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
db.Update(member);
|
db.Update(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherMemberJoin,
|
|
||||||
new Dictionary<string, object> { { "account_id", member.AccountId } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(member);
|
return Ok(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,45 +158,6 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
db.PublisherMembers.Remove(member);
|
db.PublisherMembers.Remove(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherMemberLeave,
|
|
||||||
new Dictionary<string, object> { { "account_id", member.AccountId } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{name}/members/{memberId:guid}")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult> RemoveMember(string name, Guid memberId)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var publisher = await db.Publishers
|
|
||||||
.Where(p => p.Name == name)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (publisher is null) return NotFound();
|
|
||||||
|
|
||||||
var member = await db.PublisherMembers
|
|
||||||
.Where(m => m.AccountId == memberId)
|
|
||||||
.Where(m => m.PublisherId == publisher.Id)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (member is null) return NotFound("Member was not found");
|
|
||||||
if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Manager))
|
|
||||||
return StatusCode(403, "You need at least be a manager to remove members from this publisher.");
|
|
||||||
|
|
||||||
db.PublisherMembers.Remove(member);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherMemberKick,
|
|
||||||
new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "publisher_id", publisher.Id },
|
|
||||||
{ "account_id", memberId }
|
|
||||||
}, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,11 +170,11 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
public string? PictureId { get; set; }
|
public string? PictureId { get; set; }
|
||||||
public string? BackgroundId { get; set; }
|
public string? BackgroundId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("individual")]
|
[HttpPost("individual")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[RequiredPermission("global", "publishers.create")]
|
[RequiredPermission("global", "publishers.create")]
|
||||||
public async Task<ActionResult<Publisher>> CreatePublisherIndividual([FromBody] PublisherRequest request)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> CreatePublisherIndividual([FromBody] PublisherRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
@ -258,51 +212,43 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
background
|
background
|
||||||
);
|
);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherCreate,
|
|
||||||
new Dictionary<string, object> { { "publisher_id", publisher.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(publisher);
|
return Ok(publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("organization/{realmSlug}")]
|
[HttpPost("organization/{realmSlug}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[RequiredPermission("global", "publishers.create")]
|
[RequiredPermission("global", "publishers.create")]
|
||||||
public async Task<ActionResult<Publisher>> CreatePublisherOrganization(string realmSlug,
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> CreatePublisherOrganization(string realmSlug, [FromBody] PublisherRequest request)
|
||||||
[FromBody] PublisherRequest request)
|
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
var realm = await db.Realms.FirstOrDefaultAsync(r => r.Slug == realmSlug);
|
var realm = await db.Realms.FirstOrDefaultAsync(r => r.Slug == realmSlug);
|
||||||
if (realm == null) return NotFound("Realm not found");
|
if (realm == null) return NotFound("Realm not found");
|
||||||
|
|
||||||
var isAdmin = await db.RealmMembers
|
var isAdmin = await db.RealmMembers
|
||||||
.AnyAsync(m =>
|
.AnyAsync(m => m.RealmId == realm.Id && m.AccountId == currentUser.Id && m.Role >= RealmMemberRole.Moderator);
|
||||||
m.RealmId == realm.Id && m.AccountId == currentUser.Id && m.Role >= RealmMemberRole.Moderator);
|
if (!isAdmin) return StatusCode(403, "You need to be a moderator of the realm to create an organization publisher");
|
||||||
if (!isAdmin)
|
|
||||||
return StatusCode(403, "You need to be a moderator of the realm to create an organization publisher");
|
|
||||||
|
|
||||||
var takenName = request.Name ?? realm.Slug;
|
var takenName = request.Name ?? realm.Slug;
|
||||||
var duplicateNameCount = await db.Publishers
|
var duplicateNameCount = await db.Publishers
|
||||||
.Where(p => p.Name == takenName)
|
.Where(p => p.Name == takenName)
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
if (duplicateNameCount > 0)
|
if (duplicateNameCount > 0)
|
||||||
return BadRequest("The name you requested has already been taken");
|
return BadRequest("The name you requested has already been taken");
|
||||||
|
|
||||||
CloudFile? picture = null, background = null;
|
CloudFile? picture = null, background = null;
|
||||||
if (request.PictureId is not null)
|
if (request.PictureId is not null)
|
||||||
{
|
{
|
||||||
picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync();
|
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 (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.BackgroundId is not null)
|
if (request.BackgroundId is not null)
|
||||||
{
|
{
|
||||||
background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync();
|
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 (background is null) return BadRequest("Invalid background id, unable to find the file on cloud.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var publisher = await ps.CreateOrganizationPublisher(
|
var publisher = await ps.CreateOrganizationPublisher(
|
||||||
realm,
|
realm,
|
||||||
currentUser,
|
currentUser,
|
||||||
@ -312,19 +258,15 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
picture,
|
picture,
|
||||||
background
|
background
|
||||||
);
|
);
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherCreate,
|
|
||||||
new Dictionary<string, object> { { "publisher_id", publisher.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(publisher);
|
return Ok(publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpPatch("{name}")]
|
[HttpPatch("{name}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<Publisher>> UpdatePublisher(string name, PublisherRequest request)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> UpdatePublisher(string name, PublisherRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -370,17 +312,12 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
db.Update(publisher);
|
db.Update(publisher);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherUpdate,
|
|
||||||
new Dictionary<string, object> { { "publisher_id", publisher.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(publisher);
|
return Ok(publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{name}")]
|
[HttpDelete("{name}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<Publisher>> DeletePublisher(string name)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> DeletePublisher(string name)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -408,11 +345,6 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
db.Publishers.Remove(publisher);
|
db.Publishers.Remove(publisher);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.PublisherDelete,
|
|
||||||
new Dictionary<string, object> { { "publisher_id", publisher.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Account;
|
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -9,7 +8,7 @@ namespace DysonNetwork.Sphere.Realm;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/realms")]
|
[Route("/realms")]
|
||||||
public class RealmController(AppDatabase db, RealmService rs, FileService fs, ActionLogService als) : Controller
|
public class RealmController(AppDatabase db, RealmService rs, FileService fs) : Controller
|
||||||
{
|
{
|
||||||
[HttpGet("{slug}")]
|
[HttpGet("{slug}")]
|
||||||
public async Task<ActionResult<Realm>> GetRealm(string slug)
|
public async Task<ActionResult<Realm>> GetRealm(string slug)
|
||||||
@ -92,11 +91,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
db.RealmMembers.Add(newMember);
|
db.RealmMembers.Add(newMember);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmInvite,
|
|
||||||
new Dictionary<string, object> { { "realm_id", realm.Id }, { "account_id", newMember.AccountId } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
newMember.Account = relatedUser;
|
newMember.Account = relatedUser;
|
||||||
await rs.SendInviteNotify(newMember);
|
await rs.SendInviteNotify(newMember);
|
||||||
|
|
||||||
@ -121,12 +115,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
db.Update(member);
|
db.Update(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmJoin,
|
|
||||||
new Dictionary<string, object> { { "realm_id", member.RealmId }, { "account_id", member.AccountId } },
|
|
||||||
Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(member);
|
return Ok(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,12 +135,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
db.RealmMembers.Remove(member);
|
db.RealmMembers.Remove(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmLeave,
|
|
||||||
new Dictionary<string, object> { { "realm_id", member.RealmId }, { "account_id", member.AccountId } },
|
|
||||||
Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,12 +214,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
db.RealmMembers.Remove(member);
|
db.RealmMembers.Remove(member);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmLeave,
|
|
||||||
new Dictionary<string, object> { { "realm_id", member.RealmId }, { "account_id", member.AccountId } },
|
|
||||||
Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,11 +273,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
db.Realms.Add(realm);
|
db.Realms.Add(realm);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmCreate,
|
|
||||||
new Dictionary<string, object> { { "realm_id", realm.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
if (realm.Picture is not null) await fs.MarkUsageAsync(realm.Picture, 1);
|
if (realm.Picture is not null) await fs.MarkUsageAsync(realm.Picture, 1);
|
||||||
if (realm.Background is not null) await fs.MarkUsageAsync(realm.Background, 1);
|
if (realm.Background is not null) await fs.MarkUsageAsync(realm.Background, 1);
|
||||||
|
|
||||||
@ -363,12 +334,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
|
|
||||||
db.Realms.Update(realm);
|
db.Realms.Update(realm);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmUpdate,
|
|
||||||
new Dictionary<string, object> { { "realm_id", realm.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(realm);
|
return Ok(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,11 +359,6 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
|||||||
db.Realms.Remove(realm);
|
db.Realms.Remove(realm);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
als.CreateActionLogFromRequest(
|
|
||||||
ActionLogType.RealmDelete,
|
|
||||||
new Dictionary<string, object> { { "realm_id", realm.Id } }, Request
|
|
||||||
);
|
|
||||||
|
|
||||||
if (realm.Picture is not null)
|
if (realm.Picture is not null)
|
||||||
await fs.MarkUsageAsync(realm.Picture, -1);
|
await fs.MarkUsageAsync(realm.Picture, -1);
|
||||||
if (realm.Background is not null)
|
if (realm.Background is not null)
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Developer;
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Wallet;
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
public class WalletCurrency
|
|
||||||
{
|
|
||||||
public const string SourcePoint = "points";
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum OrderStatus
|
public enum OrderStatus
|
||||||
{
|
{
|
||||||
Unpaid,
|
Unpaid,
|
||||||
@ -31,8 +25,6 @@ public class Order : ModelBase
|
|||||||
public Wallet PayeeWallet { get; set; } = null!;
|
public Wallet PayeeWallet { get; set; } = null!;
|
||||||
public Guid? TransactionId { get; set; }
|
public Guid? TransactionId { get; set; }
|
||||||
public Transaction? Transaction { get; set; }
|
public Transaction? Transaction { get; set; }
|
||||||
public Guid? IssuerAppId { get; set; }
|
|
||||||
public CustomApp? IssuerApp { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TransactionType
|
public enum TransactionType
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Wallet;
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
@ -21,36 +20,6 @@ public class PaymentService(AppDatabase db, WalletService wat)
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Transaction> CreateTransactionWithAccountAsync(
|
|
||||||
Guid? payerAccountId,
|
|
||||||
Guid? payeeAccountId,
|
|
||||||
string currency,
|
|
||||||
decimal amount,
|
|
||||||
string? remarks = null,
|
|
||||||
TransactionType type = TransactionType.System
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Wallet? payer = null, payee = null;
|
|
||||||
if (payerAccountId.HasValue)
|
|
||||||
payer = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payerAccountId.Value);
|
|
||||||
if (payeeAccountId.HasValue)
|
|
||||||
payee = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payeeAccountId.Value);
|
|
||||||
|
|
||||||
if (payer == null && payerAccountId.HasValue)
|
|
||||||
throw new ArgumentException("Payer account was specified, but wallet was not found");
|
|
||||||
if (payee == null && payeeAccountId.HasValue)
|
|
||||||
throw new ArgumentException("Payee account was specified, but wallet was not found");
|
|
||||||
|
|
||||||
return await CreateTransactionAsync(
|
|
||||||
payer?.Id,
|
|
||||||
payee?.Id,
|
|
||||||
currency,
|
|
||||||
amount,
|
|
||||||
remarks,
|
|
||||||
type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Transaction> CreateTransactionAsync(
|
public async Task<Transaction> CreateTransactionAsync(
|
||||||
Guid? payerWalletId,
|
Guid? payerWalletId,
|
||||||
Guid? payeeWalletId,
|
Guid? payeeWalletId,
|
||||||
@ -60,10 +29,6 @@ public class PaymentService(AppDatabase db, WalletService wat)
|
|||||||
TransactionType type = TransactionType.System
|
TransactionType type = TransactionType.System
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (payerWalletId == null && payeeWalletId == null)
|
|
||||||
throw new ArgumentException("At least one wallet must be specified.");
|
|
||||||
if (amount <= 0) throw new ArgumentException("Cannot create transaction with negative or zero amount.");
|
|
||||||
|
|
||||||
var transaction = new Transaction
|
var transaction = new Transaction
|
||||||
{
|
{
|
||||||
PayerWalletId = payerWalletId,
|
PayerWalletId = payerWalletId,
|
||||||
@ -76,28 +41,26 @@ public class PaymentService(AppDatabase db, WalletService wat)
|
|||||||
|
|
||||||
if (payerWalletId.HasValue)
|
if (payerWalletId.HasValue)
|
||||||
{
|
{
|
||||||
var (payerPocket, isNewlyCreated) =
|
var payerPocket = await wat.GetOrCreateWalletPocketAsync(
|
||||||
await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency);
|
(await db.Wallets.FindAsync(payerWalletId.Value))!.AccountId,
|
||||||
|
currency);
|
||||||
|
|
||||||
if (isNewlyCreated || payerPocket.Amount < amount)
|
if (payerPocket.Amount < amount)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("Insufficient funds");
|
throw new InvalidOperationException("Insufficient funds");
|
||||||
|
}
|
||||||
|
|
||||||
await db.WalletPockets
|
payerPocket.Amount -= amount;
|
||||||
.Where(p => p.Id == payerPocket.Id && p.Amount >= amount)
|
|
||||||
.ExecuteUpdateAsync(s =>
|
|
||||||
s.SetProperty(p => p.Amount, p => p.Amount - amount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payeeWalletId.HasValue)
|
if (payeeWalletId.HasValue)
|
||||||
{
|
{
|
||||||
var (payeePocket, isNewlyCreated) =
|
var payeeWallet = await db.Wallets.FindAsync(payeeWalletId.Value);
|
||||||
await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount);
|
var payeePocket = await wat.GetOrCreateWalletPocketAsync(
|
||||||
|
payeeWallet!.AccountId,
|
||||||
|
currency);
|
||||||
|
|
||||||
if (!isNewlyCreated)
|
payeePocket.Amount += amount;
|
||||||
await db.WalletPockets
|
|
||||||
.Where(p => p.Id == payeePocket.Id)
|
|
||||||
.ExecuteUpdateAsync(s =>
|
|
||||||
s.SetProperty(p => p.Amount, p => p.Amount + amount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db.PaymentTransactions.Add(transaction);
|
db.PaymentTransactions.Add(transaction);
|
||||||
@ -195,22 +158,21 @@ public class PaymentService(AppDatabase db, WalletService wat)
|
|||||||
|
|
||||||
return (order, refundTransaction);
|
return (order, refundTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency,
|
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency, decimal amount)
|
||||||
decimal amount)
|
|
||||||
{
|
{
|
||||||
var payerWallet = await wat.GetWalletAsync(payerAccountId);
|
var payerWallet = await wat.GetWalletAsync(payerAccountId);
|
||||||
if (payerWallet == null)
|
if (payerWallet == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Payer wallet not found for account {payerAccountId}");
|
throw new InvalidOperationException($"Payer wallet not found for account {payerAccountId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var payeeWallet = await wat.GetWalletAsync(payeeAccountId);
|
var payeeWallet = await wat.GetWalletAsync(payeeAccountId);
|
||||||
if (payeeWallet == null)
|
if (payeeWallet == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Payee wallet not found for account {payeeAccountId}");
|
throw new InvalidOperationException($"Payee wallet not found for account {payeeAccountId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await CreateTransactionAsync(
|
return await CreateTransactionAsync(
|
||||||
payerWallet.Id,
|
payerWallet.Id,
|
||||||
payeeWallet.Id,
|
payeeWallet.Id,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Wallet;
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
@ -20,5 +19,5 @@ public class WalletPocket : ModelBase
|
|||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
public Guid WalletId { get; set; }
|
public Guid WalletId { get; set; }
|
||||||
[JsonIgnore] public Wallet Wallet { get; set; } = null!;
|
public Wallet Wallet { get; set; } = null!;
|
||||||
}
|
}
|
@ -1,63 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Wallet;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("/wallets")]
|
|
||||||
public class WalletController(AppDatabase db, WalletService ws) : ControllerBase
|
|
||||||
{
|
|
||||||
[HttpPost]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<Wallet>> CreateWallet()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var wallet = await ws.CreateWalletAsync(currentUser.Id);
|
|
||||||
return Ok(wallet);
|
|
||||||
}
|
|
||||||
catch (Exception err)
|
|
||||||
{
|
|
||||||
return BadRequest(err.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<Wallet>> GetWallet()
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var wallet = await ws.GetWalletAsync(currentUser.Id);
|
|
||||||
if (wallet is null) return NotFound("Wallet was not found, please create one first.");
|
|
||||||
return Ok(wallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("transactions")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<List<Transaction>>> GetTransactions(
|
|
||||||
[FromQuery] int offset = 0, [FromQuery] int take = 20
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
|
||||||
|
|
||||||
var query = db.PaymentTransactions.AsQueryable()
|
|
||||||
.Include(t => t.PayeeWallet)
|
|
||||||
.Include(t => t.PayerWallet)
|
|
||||||
.Where(t => (t.PayeeWallet != null && t.PayeeWallet.AccountId == currentUser.Id) ||
|
|
||||||
(t.PayerWallet != null && t.PayerWallet.AccountId == currentUser.Id));
|
|
||||||
|
|
||||||
var transactionCount = await query.CountAsync();
|
|
||||||
var transactions = await query
|
|
||||||
.Skip(offset)
|
|
||||||
.Take(take)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
Response.Headers["X-Total"] = transactionCount.ToString();
|
|
||||||
|
|
||||||
return Ok(transactions);
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,40 +10,45 @@ public class WalletService(AppDatabase db)
|
|||||||
.Include(w => w.Pockets)
|
.Include(w => w.Pockets)
|
||||||
.FirstOrDefaultAsync(w => w.AccountId == accountId);
|
.FirstOrDefaultAsync(w => w.AccountId == accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Wallet> CreateWalletAsync(Guid accountId)
|
public async Task<Wallet> CreateWalletAsync(Guid accountId)
|
||||||
{
|
{
|
||||||
var existingWallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == accountId);
|
var wallet = new Wallet
|
||||||
if (existingWallet != null)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Wallet already exists for account {accountId}");
|
AccountId = accountId
|
||||||
}
|
};
|
||||||
|
|
||||||
var wallet = new Wallet { AccountId = accountId };
|
|
||||||
|
|
||||||
db.Wallets.Add(wallet);
|
db.Wallets.Add(wallet);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(WalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync(
|
public async Task<WalletPocket> GetOrCreateWalletPocketAsync(Guid accountId, string currency)
|
||||||
Guid walletId,
|
|
||||||
string currency,
|
|
||||||
decimal? initialAmount = null
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var pocket = await db.WalletPockets.FirstOrDefaultAsync(p => p.Currency == currency && p.WalletId == walletId);
|
var wallet = await db.Wallets
|
||||||
if (pocket != null) return (pocket, false);
|
.Include(w => w.Pockets)
|
||||||
|
.FirstOrDefaultAsync(w => w.AccountId == accountId);
|
||||||
|
|
||||||
|
if (wallet == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Wallet not found for account {accountId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var pocket = wallet.Pockets.FirstOrDefault(p => p.Currency == currency);
|
||||||
|
|
||||||
|
if (pocket != null) return pocket;
|
||||||
|
|
||||||
pocket = new WalletPocket
|
pocket = new WalletPocket
|
||||||
{
|
{
|
||||||
Currency = currency,
|
Currency = currency,
|
||||||
Amount = initialAmount ?? 0,
|
Amount = 0,
|
||||||
WalletId = walletId
|
WalletId = wallet.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wallet.Pockets.Add(pocket);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
db.WalletPockets.Add(pocket);
|
return pocket;
|
||||||
return (pocket, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,7 +43,6 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANpgsqlEntityTypeBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fccb1faacaea4420db96b09857fc56178a1600_003Fd9_003F9acf9507_003FNpgsqlEntityTypeBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANpgsqlEntityTypeBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fccb1faacaea4420db96b09857fc56178a1600_003Fd9_003F9acf9507_003FNpgsqlEntityTypeBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F6a_003Fea17bf26_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
@ -57,7 +56,6 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fdf_003F3fcdc4d2_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F12_003Fe0a28ad6_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F12_003Fe0a28ad6_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user