♻️ Moving to MagicOnion
This commit is contained in:
@ -1,7 +1,9 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using MagicOnion.Server;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
@ -9,11 +11,9 @@ namespace DysonNetwork.Pass.Account;
|
|||||||
|
|
||||||
public class AccountEventService(
|
public class AccountEventService(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
// WebSocketService ws,
|
ICacheService cache,
|
||||||
// ICacheService cache,
|
|
||||||
// PaymentService payment,
|
|
||||||
IStringLocalizer<Localization.AccountEventResource> localizer
|
IStringLocalizer<Localization.AccountEventResource> localizer
|
||||||
)
|
) : ServiceBase<IAccountEventService>, IAccountEventService
|
||||||
{
|
{
|
||||||
private static readonly Random Random = new();
|
private static readonly Random Random = new();
|
||||||
private const string StatusCacheKey = "AccountStatus_";
|
private const string StatusCacheKey = "AccountStatus_";
|
||||||
@ -21,18 +21,18 @@ public class AccountEventService(
|
|||||||
public void PurgeStatusCache(Guid userId)
|
public void PurgeStatusCache(Guid userId)
|
||||||
{
|
{
|
||||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||||
// cache.RemoveAsync(cacheKey);
|
cache.RemoveAsync(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Status> GetStatus(Guid userId)
|
public async Task<Status> GetStatus(Guid userId)
|
||||||
{
|
{
|
||||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||||
// var cachedStatus = await cache.GetAsync<Status>(cacheKey);
|
var cachedStatus = await cache.GetAsync<Status>(cacheKey);
|
||||||
// if (cachedStatus is not null)
|
if (cachedStatus is not null)
|
||||||
// {
|
{
|
||||||
// cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId);
|
cachedStatus!.IsOnline = !cachedStatus.IsInvisible /*&& ws.GetAccountIsConnected(userId)*/;
|
||||||
// return cachedStatus;
|
return cachedStatus;
|
||||||
// }
|
}
|
||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
var status = await db.AccountStatuses
|
var status = await db.AccountStatuses
|
||||||
@ -45,8 +45,12 @@ public class AccountEventService(
|
|||||||
if (status is not null)
|
if (status is not null)
|
||||||
{
|
{
|
||||||
status.IsOnline = !status.IsInvisible && isOnline;
|
status.IsOnline = !status.IsInvisible && isOnline;
|
||||||
// await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
await cache.SetWithGroupsAsync(
|
||||||
// TimeSpan.FromMinutes(5));
|
cacheKey,
|
||||||
|
status,
|
||||||
|
[$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
||||||
|
TimeSpan.FromMinutes(5)
|
||||||
|
);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +66,7 @@ public class AccountEventService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Status
|
return new Status
|
||||||
{
|
{
|
||||||
Attitude = StatusAttitude.Neutral,
|
Attitude = StatusAttitude.Neutral,
|
||||||
IsOnline = false,
|
IsOnline = false,
|
||||||
@ -88,7 +92,7 @@ public class AccountEventService(
|
|||||||
// }
|
// }
|
||||||
// else
|
// else
|
||||||
// {
|
// {
|
||||||
cacheMissUserIds.Add(userId);
|
cacheMissUserIds.Add(userId);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +196,7 @@ public class AccountEventService(
|
|||||||
return lastDate < currentDate;
|
return lastDate < currentDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public const string CheckInLockKey = "CheckInLock_";
|
private const string CheckInLockKey = "checkin-lock:";
|
||||||
|
|
||||||
public async Task<CheckInResult> CheckInDaily(Shared.Models.Account user)
|
public async Task<CheckInResult> CheckInDaily(Shared.Models.Account user)
|
||||||
{
|
{
|
||||||
@ -200,10 +204,10 @@ public class AccountEventService(
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100));
|
var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100));
|
||||||
|
|
||||||
// if (lk != null)
|
if (lk != null)
|
||||||
// await lk.ReleaseAsync();
|
await lk.ReleaseAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -211,8 +215,9 @@ public class AccountEventService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now try to acquire the lock properly
|
// Now try to acquire the lock properly
|
||||||
// await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5));
|
await using var lockObj =
|
||||||
// if (lockObj is null) throw new InvalidOperationException("Check-in was in progress.");
|
await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5));
|
||||||
|
if (lockObj is null) throw new InvalidOperationException("Check-in was in progress.");
|
||||||
|
|
||||||
var cultureInfo = new CultureInfo(user.Language, false);
|
var cultureInfo = new CultureInfo(user.Language, false);
|
||||||
CultureInfo.CurrentCulture = cultureInfo;
|
CultureInfo.CurrentCulture = cultureInfo;
|
||||||
@ -274,12 +279,53 @@ public class AccountEventService(
|
|||||||
s.SetProperty(b => b.Experience, b => b.Experience + result.RewardExperience)
|
s.SetProperty(b => b.Experience, b => b.Experience + result.RewardExperience)
|
||||||
);
|
);
|
||||||
db.AccountCheckInResults.Add(result);
|
db.AccountCheckInResults.Add(result);
|
||||||
await db.SaveChangesAsync(); // Don't forget to save changes to the database
|
await db.SaveChangesAsync(); // Remember to save changes to the database
|
||||||
|
|
||||||
// The lock will be automatically released by the await using statement
|
// The lock will be automatically released by the await using statement
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetCheckInStreak(Shared.Models.Account user)
|
||||||
|
{
|
||||||
|
var today = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
|
||||||
|
var yesterdayEnd = today.PlusDays(-1).AtMidnight().InUtc().ToInstant();
|
||||||
|
var yesterdayStart = today.PlusDays(-1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||||
|
var tomorrowEnd = today.PlusDays(1).AtMidnight().InUtc().ToInstant();
|
||||||
|
var tomorrowStart = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||||
|
|
||||||
|
var yesterdayResult = await db.AccountCheckInResults
|
||||||
|
.Where(x => x.AccountId == user.Id)
|
||||||
|
.Where(x => x.CreatedAt >= yesterdayStart)
|
||||||
|
.Where(x => x.CreatedAt < yesterdayEnd)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var tomorrowResult = await db.AccountCheckInResults
|
||||||
|
.Where(x => x.AccountId == user.Id)
|
||||||
|
.Where(x => x.CreatedAt >= tomorrowStart)
|
||||||
|
.Where(x => x.CreatedAt < tomorrowEnd)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (yesterdayResult is null && tomorrowResult is null)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
var results = await db.AccountCheckInResults
|
||||||
|
.Where(x => x.AccountId == user.Id)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var streak = 0;
|
||||||
|
var day = today;
|
||||||
|
while (results.Any(x =>
|
||||||
|
x.CreatedAt >= day.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant() &&
|
||||||
|
x.CreatedAt < day.AtMidnight().InUtc().ToInstant()))
|
||||||
|
{
|
||||||
|
streak++;
|
||||||
|
day = day.PlusDays(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return streak;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<DailyEventResponse>> GetEventCalendar(Shared.Models.Account user, int month, int year = 0,
|
public async Task<List<DailyEventResponse>> GetEventCalendar(Shared.Models.Account user, int month, int year = 0,
|
||||||
bool replaceInvisible = false)
|
bool replaceInvisible = false)
|
||||||
{
|
{
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
using DysonNetwork.Shared.Models;
|
|
||||||
using DysonNetwork.Shared.Protos.Account;
|
|
||||||
using Grpc.Core;
|
|
||||||
using Google.Protobuf.WellKnownTypes;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using DysonNetwork.Pass.Auth;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
|
||||||
|
|
||||||
public class AccountGrpcService(AppDatabase db, AuthService auth)
|
|
||||||
: DysonNetwork.Shared.Protos.Account.AccountService.AccountServiceBase
|
|
||||||
{
|
|
||||||
public override async Task<AccountResponse> GetAccount(Empty request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var account = await GetAccountFromContext(context);
|
|
||||||
return ToAccountResponse(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<AccountResponse> UpdateAccount(UpdateAccountRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var account = await GetAccountFromContext(context);
|
|
||||||
|
|
||||||
// TODO: implement
|
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return ToAccountResponse(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Shared.Models.Account> GetAccountFromContext(ServerCallContext context)
|
|
||||||
{
|
|
||||||
var authorizationHeader = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization");
|
|
||||||
if (authorizationHeader == null)
|
|
||||||
{
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Missing authorization header."));
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = authorizationHeader.Value.Replace("Bearer ", "");
|
|
||||||
if (!auth.ValidateToken(token, out var sessionId))
|
|
||||||
{
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid token."));
|
|
||||||
}
|
|
||||||
|
|
||||||
var session = await db.AuthSessions.Include(s => s.Account).ThenInclude(a => a.Contacts)
|
|
||||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Session not found."));
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.Account;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AccountResponse ToAccountResponse(Shared.Models.Account account)
|
|
||||||
{
|
|
||||||
// TODO: implement
|
|
||||||
return new AccountResponse
|
|
||||||
{
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,16 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using DysonNetwork.Pass.Auth;
|
using DysonNetwork.Pass.Auth;
|
||||||
|
using DysonNetwork.Pass.Auth.OpenId;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using EFCore.BulkExtensions;
|
using EFCore.BulkExtensions;
|
||||||
|
using MagicOnion.Server;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Pass.Account;
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ public class AccountService(
|
|||||||
// IStringLocalizer<NotificationResource> localizer,
|
// IStringLocalizer<NotificationResource> localizer,
|
||||||
ICacheService cache,
|
ICacheService cache,
|
||||||
ILogger<AccountService> logger
|
ILogger<AccountService> logger
|
||||||
)
|
) : ServiceBase<IAccountService>, IAccountService
|
||||||
{
|
{
|
||||||
public static void SetCultureInfo(Shared.Models.Account account)
|
public static void SetCultureInfo(Shared.Models.Account account)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using MagicOnion.Server;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Pass.Account;
|
||||||
@ -6,7 +8,7 @@ namespace DysonNetwork.Pass.Account;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service for handling username generation and validation
|
/// Service for handling username generation and validation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AccountUsernameService(AppDatabase db)
|
public class AccountUsernameService(AppDatabase db) : ServiceBase<IAccountUsernameService>, IAccountUsernameService
|
||||||
{
|
{
|
||||||
private readonly Random _random = new();
|
private readonly Random _random = new();
|
||||||
|
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using MagicOnion.Server;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Pass.Account;
|
||||||
|
|
||||||
public class ActionLogService(
|
public class ActionLogService : ServiceBase<IActionLogService>, IActionLogService
|
||||||
// GeoIpService geo,
|
|
||||||
// FlushBufferService fbs
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
|
// private readonly GeoIpService _geo;
|
||||||
|
// private readonly FlushBufferService _fbs;
|
||||||
|
|
||||||
|
public ActionLogService(
|
||||||
|
// GeoIpService geo,
|
||||||
|
// FlushBufferService fbs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// _geo = geo;
|
||||||
|
// _fbs = fbs;
|
||||||
|
}
|
||||||
|
|
||||||
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta)
|
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta)
|
||||||
{
|
{
|
||||||
var log = new ActionLog
|
var log = new ActionLog
|
||||||
|
@ -2,6 +2,8 @@ using System.Globalization;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using MagicOnion.Server;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
@ -12,11 +14,9 @@ namespace DysonNetwork.Pass.Account;
|
|||||||
|
|
||||||
public class MagicSpellService(
|
public class MagicSpellService(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
// EmailService email,
|
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
ILogger<MagicSpellService> logger
|
ILogger<MagicSpellService> logger
|
||||||
// IStringLocalizer<Localization.EmailResource> localizer
|
) : ServiceBase<IMagicSpellService>, IMagicSpellService
|
||||||
)
|
|
||||||
{
|
{
|
||||||
public async Task<MagicSpell> CreateMagicSpell(
|
public async Task<MagicSpell> CreateMagicSpell(
|
||||||
Shared.Models.Account account,
|
Shared.Models.Account account,
|
||||||
@ -59,6 +59,17 @@ public class MagicSpellService(
|
|||||||
return spell;
|
return spell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MagicSpell?> GetMagicSpellAsync(string token)
|
||||||
|
{
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var spell = await db.MagicSpells
|
||||||
|
.Where(s => s.Spell == token)
|
||||||
|
.Where(s => s.ExpiresAt == null || s.ExpiresAt > now)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return spell;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false)
|
public async Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false)
|
||||||
{
|
{
|
||||||
var contact = await db.AccountContacts
|
var contact = await db.AccountContacts
|
||||||
@ -144,8 +155,15 @@ public class MagicSpellService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApplyMagicSpell(MagicSpell spell)
|
public async Task ApplyMagicSpell(string token)
|
||||||
{
|
{
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var spell = await db.MagicSpells
|
||||||
|
.Where(s => s.Spell == token)
|
||||||
|
.Where(s => s.ExpiresAt == null || s.ExpiresAt > now)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (spell is null) throw new ArgumentException("Magic spell not found.");
|
||||||
|
|
||||||
switch (spell.Type)
|
switch (spell.Type)
|
||||||
{
|
{
|
||||||
case MagicSpellType.AuthPasswordReset:
|
case MagicSpellType.AuthPasswordReset:
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using EFCore.BulkExtensions;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using EFCore.BulkExtensions;
|
||||||
|
using MagicOnion.Server;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Pass.Account;
|
||||||
|
|
||||||
public class NotificationService(
|
public class NotificationService(
|
||||||
AppDatabase db
|
AppDatabase db,
|
||||||
// WebSocketService ws,
|
IConfiguration config,
|
||||||
// IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory
|
||||||
// IConfiguration config
|
) : ServiceBase<INotificationService>, INotificationService
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// private readonly string _notifyTopic = config["Notifications:Topic"]!;
|
private readonly string _notifyTopic = config["Notifications:Topic"]!;
|
||||||
// private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
|
private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
|
||||||
|
|
||||||
public async Task UnsubscribePushNotifications(string deviceId)
|
public async Task UnsubscribePushNotifications(string deviceId)
|
||||||
{
|
{
|
||||||
// await db.NotificationPushSubscriptions
|
await db.NotificationPushSubscriptions
|
||||||
// .Where(s => s.DeviceId == deviceId)
|
.Where(s => s.DeviceId == deviceId)
|
||||||
// .ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<NotificationPushSubscription> SubscribePushNotification(
|
public async Task<NotificationPushSubscription> SubscribePushNotification(
|
||||||
@ -36,27 +36,27 @@ public class NotificationService(
|
|||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
|
||||||
// First check if a matching subscription exists
|
// First check if a matching subscription exists
|
||||||
// var existingSubscription = await db.NotificationPushSubscriptions
|
var existingSubscription = await db.NotificationPushSubscriptions
|
||||||
// .Where(s => s.AccountId == account.Id)
|
.Where(s => s.AccountId == account.Id)
|
||||||
// .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
|
.Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
|
||||||
// .FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
// if (existingSubscription is not null)
|
if (existingSubscription is not null)
|
||||||
// {
|
{
|
||||||
// // Update the existing subscription directly in the database
|
// Update the existing subscription directly in the database
|
||||||
// await db.NotificationPushSubscriptions
|
await db.NotificationPushSubscriptions
|
||||||
// .Where(s => s.Id == existingSubscription.Id)
|
.Where(s => s.Id == existingSubscription.Id)
|
||||||
// .ExecuteUpdateAsync(setters => setters
|
.ExecuteUpdateAsync(setters => setters
|
||||||
// .SetProperty(s => s.DeviceId, deviceId)
|
.SetProperty(s => s.DeviceId, deviceId)
|
||||||
// .SetProperty(s => s.DeviceToken, deviceToken)
|
.SetProperty(s => s.DeviceToken, deviceToken)
|
||||||
// .SetProperty(s => s.UpdatedAt, now));
|
.SetProperty(s => s.UpdatedAt, now));
|
||||||
|
|
||||||
// // Return the updated subscription
|
// Return the updated subscription
|
||||||
// existingSubscription.DeviceId = deviceId;
|
existingSubscription.DeviceId = deviceId;
|
||||||
// existingSubscription.DeviceToken = deviceToken;
|
existingSubscription.DeviceToken = deviceToken;
|
||||||
// existingSubscription.UpdatedAt = now;
|
existingSubscription.UpdatedAt = now;
|
||||||
// return existingSubscription;
|
return existingSubscription;
|
||||||
// }
|
}
|
||||||
|
|
||||||
var subscription = new NotificationPushSubscription
|
var subscription = new NotificationPushSubscription
|
||||||
{
|
{
|
||||||
@ -102,11 +102,12 @@ public class NotificationService(
|
|||||||
|
|
||||||
if (save)
|
if (save)
|
||||||
{
|
{
|
||||||
// db.Add(notification);
|
db.Add(notification);
|
||||||
// await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSilent) Console.WriteLine("Simulating notification delivery."); // _ = DeliveryNotification(notification);
|
if (!isSilent)
|
||||||
|
Console.WriteLine("Simulating notification delivery."); // _ = DeliveryNotification(notification);
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
@ -120,11 +121,11 @@ public class NotificationService(
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
// Pushing the notification
|
// Pushing the notification
|
||||||
// var subscribers = await db.NotificationPushSubscriptions
|
var subscribers = await db.NotificationPushSubscriptions
|
||||||
// .Where(s => s.AccountId == notification.AccountId)
|
.Where(s => s.AccountId == notification.AccountId)
|
||||||
// .ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// await _PushNotification(notification, subscribers);
|
await _PushNotification(notification, subscribers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MarkNotificationsViewed(ICollection<Notification> notifications)
|
public async Task MarkNotificationsViewed(ICollection<Notification> notifications)
|
||||||
@ -174,12 +175,13 @@ public class NotificationService(
|
|||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
// var subscribers = await db.NotificationPushSubscriptions
|
var subscribers = await db.NotificationPushSubscriptions
|
||||||
// .ToListAsync();
|
.ToListAsync();
|
||||||
// await _PushNotification(notification, subscribers);
|
await _PushNotification(notification, subscribers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendNotificationBatch(Notification notification, List<Shared.Models.Account> accounts, bool save = false)
|
public async Task SendNotificationBatch(Notification notification, List<Shared.Models.Account> accounts,
|
||||||
|
bool save = false)
|
||||||
{
|
{
|
||||||
if (save)
|
if (save)
|
||||||
{
|
{
|
||||||
@ -198,7 +200,7 @@ public class NotificationService(
|
|||||||
};
|
};
|
||||||
return newNotification;
|
return newNotification;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
// await db.BulkInsertAsync(notifications);
|
await db.BulkInsertAsync(notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var account in accounts)
|
foreach (var account in accounts)
|
||||||
@ -219,93 +221,93 @@ public class NotificationService(
|
|||||||
// await _PushNotification(notification, subscribers);
|
// await _PushNotification(notification, subscribers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification,
|
private List<Dictionary<string, object>> _BuildNotificationPayload(Notification notification,
|
||||||
// IEnumerable<NotificationPushSubscription> subscriptions)
|
IEnumerable<NotificationPushSubscription> subscriptions)
|
||||||
// {
|
{
|
||||||
// var subDict = subscriptions
|
var subDict = subscriptions
|
||||||
// .GroupBy(x => x.Provider)
|
.GroupBy(x => x.Provider)
|
||||||
// .ToDictionary(x => x.Key, x => x.ToList());
|
.ToDictionary(x => x.Key, x => x.ToList());
|
||||||
|
|
||||||
// var notifications = subDict.Select(value =>
|
var notifications = subDict.Select(value =>
|
||||||
// {
|
{
|
||||||
// var platformCode = value.Key switch
|
var platformCode = value.Key switch
|
||||||
// {
|
{
|
||||||
// NotificationPushProvider.Apple => 1,
|
NotificationPushProvider.Apple => 1,
|
||||||
// NotificationPushProvider.Google => 2,
|
NotificationPushProvider.Google => 2,
|
||||||
// _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}")
|
_ => throw new InvalidOperationException($"Unknown push provider: {value.Key}")
|
||||||
// };
|
};
|
||||||
|
|
||||||
// var tokens = value.Value.Select(x => x.DeviceToken).ToList();
|
var tokens = value.Value.Select(x => x.DeviceToken).ToList();
|
||||||
// return _BuildNotificationPayload(notification, platformCode, tokens);
|
return _BuildNotificationPayload(notification, platformCode, tokens);
|
||||||
// }).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// return notifications.ToList();
|
return notifications.ToList();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode,
|
private Dictionary<string, object> _BuildNotificationPayload(Notification notification, int platformCode,
|
||||||
// IEnumerable<string> deviceTokens)
|
IEnumerable<string> deviceTokens)
|
||||||
// {
|
{
|
||||||
// var alertDict = new Dictionary<string, object>();
|
var alertDict = new Dictionary<string, object>();
|
||||||
// var dict = new Dictionary<string, object>
|
var dict = new Dictionary<string, object>
|
||||||
// {
|
{
|
||||||
// ["notif_id"] = notification.Id.ToString(),
|
["notif_id"] = notification.Id.ToString(),
|
||||||
// ["apns_id"] = notification.Id.ToString(),
|
["apns_id"] = notification.Id.ToString(),
|
||||||
// ["topic"] = _notifyTopic,
|
["topic"] = _notifyTopic,
|
||||||
// ["tokens"] = deviceTokens,
|
["tokens"] = deviceTokens,
|
||||||
// ["data"] = new Dictionary<string, object>
|
["data"] = new Dictionary<string, object>
|
||||||
// {
|
{
|
||||||
// ["type"] = notification.Topic,
|
["type"] = notification.Topic,
|
||||||
// ["meta"] = notification.Meta ?? new Dictionary<string, object>(),
|
["meta"] = notification.Meta ?? new Dictionary<string, object>(),
|
||||||
// },
|
},
|
||||||
// ["mutable_content"] = true,
|
["mutable_content"] = true,
|
||||||
// ["priority"] = notification.Priority >= 5 ? "high" : "normal",
|
["priority"] = notification.Priority >= 5 ? "high" : "normal",
|
||||||
// };
|
};
|
||||||
|
|
||||||
// if (!string.IsNullOrWhiteSpace(notification.Title))
|
if (!string.IsNullOrWhiteSpace(notification.Title))
|
||||||
// {
|
{
|
||||||
// dict["title"] = notification.Title;
|
dict["title"] = notification.Title;
|
||||||
// alertDict["title"] = notification.Title;
|
alertDict["title"] = notification.Title;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (!string.IsNullOrWhiteSpace(notification.Content))
|
if (!string.IsNullOrWhiteSpace(notification.Content))
|
||||||
// {
|
{
|
||||||
// dict["message"] = notification.Content;
|
dict["message"] = notification.Content;
|
||||||
// alertDict["body"] = notification.Content;
|
alertDict["body"] = notification.Content;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (!string.IsNullOrWhiteSpace(notification.Subtitle))
|
if (!string.IsNullOrWhiteSpace(notification.Subtitle))
|
||||||
// {
|
{
|
||||||
// dict["message"] = $"{notification.Subtitle}\n{dict["message"]}";
|
dict["message"] = $"{notification.Subtitle}\n{dict["message"]}";
|
||||||
// alertDict["subtitle"] = notification.Subtitle;
|
alertDict["subtitle"] = notification.Subtitle;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if (notification.Priority >= 5)
|
if (notification.Priority >= 5)
|
||||||
// dict["name"] = "default";
|
dict["name"] = "default";
|
||||||
|
|
||||||
// dict["platform"] = platformCode;
|
dict["platform"] = platformCode;
|
||||||
// dict["alert"] = alertDict;
|
dict["alert"] = alertDict;
|
||||||
|
|
||||||
// return dict;
|
return dict;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// private async Task _PushNotification(Notification notification,
|
private async Task _PushNotification(Notification notification,
|
||||||
// IEnumerable<NotificationPushSubscription> subscriptions)
|
IEnumerable<NotificationPushSubscription> subscriptions)
|
||||||
// {
|
{
|
||||||
// var subList = subscriptions.ToList();
|
var subList = subscriptions.ToList();
|
||||||
// if (subList.Count == 0) return;
|
if (subList.Count == 0) return;
|
||||||
|
|
||||||
// var requestDict = new Dictionary<string, object>
|
var requestDict = new Dictionary<string, object>
|
||||||
// {
|
{
|
||||||
// ["notifications"] = _BuildNotificationPayload(notification, subList)
|
["notifications"] = _BuildNotificationPayload(notification, subList)
|
||||||
// };
|
};
|
||||||
|
|
||||||
// var client = httpFactory.CreateClient();
|
var client = httpFactory.CreateClient();
|
||||||
// client.BaseAddress = _notifyEndpoint;
|
client.BaseAddress = _notifyEndpoint;
|
||||||
// var request = await client.PostAsync("/push", new StringContent(
|
var request = await client.PostAsync("/push", new StringContent(
|
||||||
// JsonSerializer.Serialize(requestDict),
|
JsonSerializer.Serialize(requestDict),
|
||||||
// Encoding.UTF8,
|
Encoding.UTF8,
|
||||||
// "application/json"
|
"application/json"
|
||||||
// ));
|
));
|
||||||
// request.EnsureSuccessStatusCode();
|
request.EnsureSuccessStatusCode();
|
||||||
// }
|
}
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using MagicOnion.Server;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Pass.Account;
|
||||||
|
|
||||||
public class RelationshipService(
|
public class RelationshipService(AppDatabase db, ICacheService cache) : ServiceBase<IRelationshipService>, IRelationshipService
|
||||||
AppDatabase db
|
|
||||||
// ICacheService cache
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
|
private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
|
||||||
private const string UserBlockedCacheKeyPrefix = "accounts:blocked:";
|
private const string UserBlockedCacheKeyPrefix = "accounts:blocked:";
|
||||||
@ -150,7 +150,7 @@ public class RelationshipService(
|
|||||||
db.Update(relationship);
|
db.Update(relationship);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
// await PurgeRelationshipCache(accountId, relatedId);
|
await PurgeRelationshipCache(accountId, relatedId);
|
||||||
|
|
||||||
return relationship;
|
return relationship;
|
||||||
}
|
}
|
||||||
@ -158,8 +158,7 @@ public class RelationshipService(
|
|||||||
public async Task<List<Guid>> ListAccountFriends(Shared.Models.Account account)
|
public async Task<List<Guid>> ListAccountFriends(Shared.Models.Account account)
|
||||||
{
|
{
|
||||||
var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}";
|
var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}";
|
||||||
// var friends = await cache.GetAsync<List<Guid>>(cacheKey);
|
var friends = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||||
var friends = new List<Guid>(); // Placeholder
|
|
||||||
|
|
||||||
if (friends == null)
|
if (friends == null)
|
||||||
{
|
{
|
||||||
@ -169,17 +168,16 @@ public class RelationshipService(
|
|||||||
.Select(r => r.AccountId)
|
.Select(r => r.AccountId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1));
|
await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
return friends ?? [];
|
return friends;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Guid>> ListAccountBlocked(Shared.Models.Account account)
|
public async Task<List<Guid>> ListAccountBlocked(Shared.Models.Account account)
|
||||||
{
|
{
|
||||||
var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}";
|
var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}";
|
||||||
// var blocked = await cache.GetAsync<List<Guid>>(cacheKey);
|
var blocked = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||||
var blocked = new List<Guid>(); // Placeholder
|
|
||||||
|
|
||||||
if (blocked == null)
|
if (blocked == null)
|
||||||
{
|
{
|
||||||
@ -189,10 +187,10 @@ public class RelationshipService(
|
|||||||
.Select(r => r.AccountId)
|
.Select(r => r.AccountId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1));
|
await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocked ?? [];
|
return blocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> HasRelationshipWithStatus(Guid accountId, Guid relatedId,
|
public async Task<bool> HasRelationshipWithStatus(Guid accountId, Guid relatedId,
|
||||||
@ -204,9 +202,9 @@ public class RelationshipService(
|
|||||||
|
|
||||||
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId)
|
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId)
|
||||||
{
|
{
|
||||||
// await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||||
// await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||||
// await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}");
|
await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}");
|
||||||
// await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}");
|
await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,96 +0,0 @@
|
|||||||
using DysonNetwork.Shared.Protos.Auth;
|
|
||||||
using Grpc.Core;
|
|
||||||
using Google.Protobuf.WellKnownTypes;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using NodaTime;
|
|
||||||
using System.Text.Json;
|
|
||||||
using DysonNetwork.Shared.Models;
|
|
||||||
using DysonNetwork.Pass.Account;
|
|
||||||
using Challenge = DysonNetwork.Shared.Models.Challenge;
|
|
||||||
using Session = DysonNetwork.Shared.Models.Session;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Auth;
|
|
||||||
|
|
||||||
public class AuthGrpcService(AppDatabase db, AccountService accounts, AuthService auth)
|
|
||||||
: DysonNetwork.Shared.Protos.Auth.AuthService.AuthServiceBase
|
|
||||||
{
|
|
||||||
public override async Task<LoginResponse> Login(LoginRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var account = await accounts.LookupAccount(request.Username);
|
|
||||||
if (account == null)
|
|
||||||
{
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found."));
|
|
||||||
}
|
|
||||||
|
|
||||||
var factor = await db.AccountAuthFactors.FirstOrDefaultAsync(f => f.AccountId == account.Id && f.Type == AccountAuthFactorType.Password);
|
|
||||||
if (factor == null || !factor.VerifyPassword(request.Password))
|
|
||||||
{
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid credentials."));
|
|
||||||
}
|
|
||||||
|
|
||||||
var session = new Session
|
|
||||||
{
|
|
||||||
LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow),
|
|
||||||
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)),
|
|
||||||
Account = account,
|
|
||||||
Challenge = new Challenge()
|
|
||||||
};
|
|
||||||
|
|
||||||
db.AuthSessions.Add(session);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var token = auth.CreateToken(session);
|
|
||||||
|
|
||||||
return new LoginResponse
|
|
||||||
{
|
|
||||||
AccessToken = token,
|
|
||||||
ExpiresIn = (long)(session.ExpiredAt.Value - session.LastGrantedAt.Value).TotalSeconds
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IntrospectionResponse> IntrospectToken(IntrospectTokenRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (auth.ValidateToken(request.Token, out var sessionId))
|
|
||||||
{
|
|
||||||
var session = await db.AuthSessions
|
|
||||||
.Include(s => s.Account)
|
|
||||||
.Include(s => s.Challenge)
|
|
||||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
|
||||||
|
|
||||||
if (session != null)
|
|
||||||
{
|
|
||||||
return new IntrospectionResponse
|
|
||||||
{
|
|
||||||
Active = true,
|
|
||||||
Claims = JsonSerializer.Serialize(new { sub = session.AccountId }),
|
|
||||||
ClientId = session.AppId?.ToString() ?? "",
|
|
||||||
Username = session.Account.Name,
|
|
||||||
Scope = string.Join(" ", session.Challenge.Scopes),
|
|
||||||
Iat = Timestamp.FromDateTime(session.CreatedAt.ToDateTimeUtc()),
|
|
||||||
Exp = Timestamp.FromDateTime(session.ExpiredAt?.ToDateTimeUtc() ?? DateTime.MaxValue)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new IntrospectionResponse { Active = false };
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Empty> Logout(Empty request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
var authorizationHeader = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization");
|
|
||||||
if (authorizationHeader != null)
|
|
||||||
{
|
|
||||||
var token = authorizationHeader.Value.Replace("Bearer ", "");
|
|
||||||
if (auth.ValidateToken(token, out var sessionId))
|
|
||||||
{
|
|
||||||
var session = await db.AuthSessions.FindAsync(sessionId);
|
|
||||||
if (session != null)
|
|
||||||
{
|
|
||||||
db.AuthSessions.Remove(session);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Empty();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Auth.OpenId;
|
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
|
||||||
public class AppleMobileConnectRequest
|
public class AppleMobileConnectRequest
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,7 @@ using System.Text;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using DysonNetwork.Pass.Account;
|
using DysonNetwork.Pass.Account;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Auth.OpenId;
|
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Auth.OpenId;
|
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Auth.OpenId;
|
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using DysonNetwork.Pass.Account;
|
using DysonNetwork.Pass.Account;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
@ -3,7 +3,6 @@ using System.Net.Http.Json;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Auth.OpenId;
|
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the state parameter used in OpenID Connect flows.
|
/// Represents the state parameter used in OpenID Connect flows.
|
||||||
|
@ -9,24 +9,27 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
|
<PackageReference Include="MagicOnion.Client" Version="7.0.5" />
|
||||||
|
<PackageReference Include="MagicOnion.Server" Version="7.0.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
|
||||||
|
<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.NetTopologySuite" Version="9.0.4" />
|
<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="StackExchange.Redis" Version="2.8.41" />
|
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
|
||||||
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0" />
|
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0-preview.6.24328.4" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0-preview1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.12.1" />
|
||||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using DysonNetwork.Pass;
|
using DysonNetwork.Pass;
|
||||||
using DysonNetwork.Pass.Account;
|
using DysonNetwork.Pass.Account;
|
||||||
using DysonNetwork.Pass.Auth;
|
using DysonNetwork.Pass.Auth;
|
||||||
|
using DysonNetwork.Pass.Startup;
|
||||||
|
using DysonNetwork.Shared.Startup;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@ -8,9 +10,15 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
builder.ConfigureAppKestrel();
|
||||||
|
builder.Services.AddAppSwagger();
|
||||||
|
builder.Services.AddAppAuthentication();
|
||||||
|
builder.Services.AddAppRateLimiting();
|
||||||
|
builder.Services.AddAppBusinessServices(builder.Configuration);
|
||||||
|
builder.Services.AddAppServices(builder.Configuration);
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddGrpc();
|
builder.Services.AddMagicOnion();
|
||||||
builder.Services.AddDbContext<AppDatabase>(options =>
|
builder.Services.AddDbContext<AppDatabase>(options =>
|
||||||
options.UseNpgsql(builder.Configuration.GetConnectionString("App")));
|
options.UseNpgsql(builder.Configuration.GetConnectionString("App")));
|
||||||
|
|
||||||
@ -21,10 +29,10 @@ var app = builder.Build();
|
|||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
app.ConfigureAppMiddleware(builder.Configuration);
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.MapGrpcService<AccountGrpcService>();
|
app.MapMagicOnionService();
|
||||||
app.MapGrpcService<AuthGrpcService>();
|
|
||||||
|
|
||||||
// Run database migrations
|
// Run database migrations
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
|
186
DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs
Normal file
186
DysonNetwork.Pass/Startup/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.RateLimiting;
|
||||||
|
using DysonNetwork.Pass.Account;
|
||||||
|
using DysonNetwork.Pass.Auth;
|
||||||
|
using DysonNetwork.Pass.Auth.OidcProvider.Options;
|
||||||
|
using DysonNetwork.Pass.Auth.OidcProvider.Services;
|
||||||
|
using DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
using DysonNetwork.Pass.Localization;
|
||||||
|
using DysonNetwork.Pass.Permission;
|
||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
|
using DysonNetwork.Shared.Services;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using NodaTime;
|
||||||
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Startup;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||||
|
|
||||||
|
services.AddDbContext<AppDatabase>();
|
||||||
|
services.AddSingleton<IConnectionMultiplexer>(_ =>
|
||||||
|
{
|
||||||
|
var connection = configuration.GetConnectionString("FastRetrieve")!;
|
||||||
|
return ConnectionMultiplexer.Connect(connection);
|
||||||
|
});
|
||||||
|
services.AddSingleton<IClock>(SystemClock.Instance);
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
services.AddSingleton<ICacheService, CacheServiceRedis>();
|
||||||
|
|
||||||
|
services.AddHttpClient();
|
||||||
|
|
||||||
|
// Register MagicOnion services
|
||||||
|
services.AddScoped<IAccountService, AccountService>();
|
||||||
|
services.AddScoped<INotificationService, NotificationService>();
|
||||||
|
services.AddScoped<IRelationshipService, RelationshipService>();
|
||||||
|
services.AddScoped<IActionLogService, ActionLogService>();
|
||||||
|
services.AddScoped<IAccountUsernameService, AccountUsernameService>();
|
||||||
|
services.AddScoped<IMagicSpellService, MagicSpellService>();
|
||||||
|
services.AddScoped<IAccountEventService, AccountEventService>();
|
||||||
|
|
||||||
|
// Register OIDC services
|
||||||
|
services.AddScoped<OidcService, GoogleOidcService>();
|
||||||
|
services.AddScoped<OidcService, AppleOidcService>();
|
||||||
|
services.AddScoped<OidcService, GitHubOidcService>();
|
||||||
|
services.AddScoped<OidcService, MicrosoftOidcService>();
|
||||||
|
services.AddScoped<OidcService, DiscordOidcService>();
|
||||||
|
services.AddScoped<OidcService, AfdianOidcService>();
|
||||||
|
services.AddScoped<GoogleOidcService>();
|
||||||
|
services.AddScoped<AppleOidcService>();
|
||||||
|
services.AddScoped<GitHubOidcService>();
|
||||||
|
services.AddScoped<MicrosoftOidcService>();
|
||||||
|
services.AddScoped<DiscordOidcService>();
|
||||||
|
services.AddScoped<AfdianOidcService>();
|
||||||
|
|
||||||
|
services.AddControllers().AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||||
|
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||||
|
|
||||||
|
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||||
|
}).AddDataAnnotationsLocalization(options =>
|
||||||
|
{
|
||||||
|
options.DataAnnotationLocalizerProvider = (type, factory) =>
|
||||||
|
factory.Create(typeof(SharedResource));
|
||||||
|
});
|
||||||
|
services.AddRazorPages();
|
||||||
|
|
||||||
|
services.Configure<RequestLocalizationOptions>(options =>
|
||||||
|
{
|
||||||
|
var supportedCultures = new[]
|
||||||
|
{
|
||||||
|
new CultureInfo("en-US"),
|
||||||
|
new CultureInfo("zh-Hans"),
|
||||||
|
};
|
||||||
|
|
||||||
|
options.SupportedCultures = supportedCultures;
|
||||||
|
options.SupportedUICultures = supportedCultures;
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAppRateLimiting(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddRateLimiter(o => o.AddFixedWindowLimiter(policyName: "fixed", opts =>
|
||||||
|
{
|
||||||
|
opts.Window = TimeSpan.FromMinutes(1);
|
||||||
|
opts.PermitLimit = 120;
|
||||||
|
opts.QueueLimit = 2;
|
||||||
|
opts.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAppAuthentication(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddCors();
|
||||||
|
services.AddAuthorization();
|
||||||
|
services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = AuthConstants.SchemeName;
|
||||||
|
options.DefaultChallengeScheme = AuthConstants.SchemeName;
|
||||||
|
})
|
||||||
|
.AddScheme<DysonTokenAuthOptions, DysonTokenAuthHandler>(AuthConstants.SchemeName, _ => { });
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAppSwagger(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddEndpointsApiExplorer();
|
||||||
|
services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.SwaggerDoc("v1", new OpenApiInfo
|
||||||
|
{
|
||||||
|
Version = "v1",
|
||||||
|
Title = "Solar Network API",
|
||||||
|
Description = "An open-source social network",
|
||||||
|
TermsOfService = new Uri("https://solsynth.dev/terms"),
|
||||||
|
License = new OpenApiLicense
|
||||||
|
{
|
||||||
|
Name = "APGLv3",
|
||||||
|
Url = new Uri("https://www.gnu.org/licenses/agpl-3.0.html")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Description = "Please enter a valid token",
|
||||||
|
Name = "Authorization",
|
||||||
|
Type = SecuritySchemeType.Http,
|
||||||
|
BearerFormat = "JWT",
|
||||||
|
Scheme = "Bearer"
|
||||||
|
});
|
||||||
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "Bearer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
services.AddOpenApi();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services,
|
||||||
|
IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddScoped<CompactTokenService>();
|
||||||
|
services.AddScoped<PermissionService>();
|
||||||
|
services.AddScoped<ActionLogService>();
|
||||||
|
services.AddScoped<AccountService>();
|
||||||
|
services.AddScoped<AccountEventService>();
|
||||||
|
services.AddScoped<ActionLogService>();
|
||||||
|
services.AddScoped<RelationshipService>();
|
||||||
|
services.AddScoped<MagicSpellService>();
|
||||||
|
services.AddScoped<NotificationService>();
|
||||||
|
services.AddScoped<AuthService>();
|
||||||
|
services.AddScoped<AccountUsernameService>();
|
||||||
|
|
||||||
|
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
||||||
|
services.AddScoped<OidcProviderService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
@ -12,13 +12,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
|
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.27.2" />
|
<PackageReference Include="Google.Protobuf" Version="3.27.2" />
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.65.0">
|
<PackageReference Include="MagicOnion.Client" Version="7.0.5" />
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
|
||||||
</PackageReference>
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
|
||||||
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
|
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||||
@ -27,4 +27,10 @@
|
|||||||
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
|
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Microsoft.AspNetCore">
|
||||||
|
<HintPath>..\..\..\..\..\..\opt\homebrew\Cellar\dotnet\9.0.6\libexec\shared\Microsoft.AspNetCore.App\9.0.6\Microsoft.AspNetCore.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
public enum MagicSpellType
|
public enum MagicSpellType
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
namespace DysonNetwork.Sphere.Auth.OpenId;
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the user information from an OIDC provider
|
/// Represents the user information from an OIDC provider
|
@ -1,51 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package dyson_network.sphere.account;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
|
|
||||||
option csharp_namespace = "DysonNetwork.Shared.Protos.Account";
|
|
||||||
|
|
||||||
service AccountService {
|
|
||||||
rpc GetAccount(google.protobuf.Empty) returns (AccountResponse);
|
|
||||||
rpc UpdateAccount(UpdateAccountRequest) returns (AccountResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message AccountResponse {
|
|
||||||
string id = 1;
|
|
||||||
string name = 2;
|
|
||||||
string nick = 3;
|
|
||||||
string language = 4;
|
|
||||||
google.protobuf.Timestamp activated_at = 5;
|
|
||||||
bool is_superuser = 6;
|
|
||||||
Profile profile = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Profile {
|
|
||||||
string first_name = 1;
|
|
||||||
string last_name = 2;
|
|
||||||
string bio = 3;
|
|
||||||
string gender = 4;
|
|
||||||
string pronouns = 5;
|
|
||||||
string time_zone = 6;
|
|
||||||
string location = 7;
|
|
||||||
google.protobuf.Timestamp birthday = 8;
|
|
||||||
google.protobuf.Timestamp last_seen_at = 9;
|
|
||||||
int32 experience = 10;
|
|
||||||
int32 level = 11;
|
|
||||||
double leveling_progress = 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateAccountRequest {
|
|
||||||
optional string nick = 1;
|
|
||||||
optional string language = 2;
|
|
||||||
optional string first_name = 3;
|
|
||||||
optional string last_name = 4;
|
|
||||||
optional string bio = 5;
|
|
||||||
optional string gender = 6;
|
|
||||||
optional string pronouns = 7;
|
|
||||||
optional string time_zone = 8;
|
|
||||||
optional string location = 9;
|
|
||||||
optional google.protobuf.Timestamp birthday = 10;
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package dyson_network.sphere.auth;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
|
|
||||||
option csharp_namespace = "DysonNetwork.Shared.Protos.Auth";
|
|
||||||
|
|
||||||
service AuthService {
|
|
||||||
rpc Login(LoginRequest) returns (LoginResponse);
|
|
||||||
rpc IntrospectToken(IntrospectTokenRequest) returns (IntrospectionResponse);
|
|
||||||
rpc Logout(google.protobuf.Empty) returns (google.protobuf.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginRequest {
|
|
||||||
string username = 1;
|
|
||||||
string password = 2;
|
|
||||||
optional string two_factor_code = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginResponse {
|
|
||||||
string access_token = 1;
|
|
||||||
string refresh_token = 2;
|
|
||||||
int64 expires_in = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IntrospectTokenRequest {
|
|
||||||
string token = 1;
|
|
||||||
optional string token_type_hint = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IntrospectionResponse {
|
|
||||||
bool active = 1;
|
|
||||||
string claims = 2;
|
|
||||||
string client_id = 3;
|
|
||||||
string username = 4;
|
|
||||||
string scope = 5;
|
|
||||||
google.protobuf.Timestamp iat = 6;
|
|
||||||
google.protobuf.Timestamp exp = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Session {
|
|
||||||
string id = 1;
|
|
||||||
string label = 2;
|
|
||||||
google.protobuf.Timestamp last_granted_at = 3;
|
|
||||||
google.protobuf.Timestamp expired_at = 4;
|
|
||||||
string account_id = 5;
|
|
||||||
string challenge_id = 6;
|
|
||||||
optional string app_id = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Challenge {
|
|
||||||
string id = 1;
|
|
||||||
google.protobuf.Timestamp expired_at = 2;
|
|
||||||
int32 step_remain = 3;
|
|
||||||
int32 step_total = 4;
|
|
||||||
int32 failed_attempts = 5;
|
|
||||||
string platform = 6;
|
|
||||||
string type = 7;
|
|
||||||
repeated string blacklist_factors = 8;
|
|
||||||
repeated string audiences = 9;
|
|
||||||
repeated string scopes = 10;
|
|
||||||
string ip_address = 11;
|
|
||||||
string user_agent = 12;
|
|
||||||
optional string device_id = 13;
|
|
||||||
optional string nonce = 14;
|
|
||||||
string account_id = 15;
|
|
||||||
}
|
|
28
DysonNetwork.Shared/Services/IAccountEventService.cs
Normal file
28
DysonNetwork.Shared/Services/IAccountEventService.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using MagicOnion;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface IAccountEventService : IService<IAccountEventService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Purges the status cache for a user
|
||||||
|
/// </summary>
|
||||||
|
void PurgeStatusCache(Guid userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the status of a user
|
||||||
|
/// </summary>
|
||||||
|
Task<Status> GetStatus(Guid userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a daily check-in for a user
|
||||||
|
/// </summary>
|
||||||
|
Task<CheckInResult> CheckInDaily(Account user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the check-in streak for a user
|
||||||
|
/// </summary>
|
||||||
|
Task<int> GetCheckInStreak(Account user);
|
||||||
|
}
|
62
DysonNetwork.Shared/Services/IAccountService.cs
Normal file
62
DysonNetwork.Shared/Services/IAccountService.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using MagicOnion;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface IAccountService : IService<IAccountService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all cached data for the specified account
|
||||||
|
/// </summary>
|
||||||
|
Task PurgeAccountCache(Account account);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up an account by username or contact information
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="probe">Username or contact information to search for</param>
|
||||||
|
/// <returns>The matching account if found, otherwise null</returns>
|
||||||
|
Task<Account?> LookupAccount(string probe);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up an account by external authentication provider connection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier">The provider's unique identifier for the user</param>
|
||||||
|
/// <param name="provider">The name of the authentication provider</param>
|
||||||
|
/// <returns>The matching account if found, otherwise null</returns>
|
||||||
|
Task<Account?> LookupAccountByConnection(string identifier, string provider);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the account level for the specified account ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">The ID of the account</param>
|
||||||
|
/// <returns>The account level if found, otherwise null</returns>
|
||||||
|
Task<int?> GetAccountLevel(Guid accountId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new account with the specified details
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The account username</param>
|
||||||
|
/// <param name="nick">The display name/nickname</param>
|
||||||
|
/// <param name="email">The primary email address</param>
|
||||||
|
/// <param name="password">The account password (optional, can be set later)</param>
|
||||||
|
/// <param name="language">The preferred language (defaults to en-US)</param>
|
||||||
|
/// <param name="isEmailVerified">Whether the email is verified (defaults to false)</param>
|
||||||
|
/// <param name="isActivated">Whether the account is activated (defaults to false)</param>
|
||||||
|
/// <returns>The newly created account</returns>
|
||||||
|
Task<Account> CreateAccount(
|
||||||
|
string name,
|
||||||
|
string nick,
|
||||||
|
string email,
|
||||||
|
string? password,
|
||||||
|
string language = "en-US",
|
||||||
|
bool isEmailVerified = false,
|
||||||
|
bool isActivated = false
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new account using OpenID Connect user information
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userInfo">The OpenID Connect user information</param>
|
||||||
|
/// <returns>The newly created account</returns>
|
||||||
|
Task<Account> CreateAccount(OidcUserInfo userInfo);
|
||||||
|
}
|
23
DysonNetwork.Shared/Services/IAccountUsernameService.cs
Normal file
23
DysonNetwork.Shared/Services/IAccountUsernameService.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using MagicOnion;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface IAccountUsernameService : IService<IAccountUsernameService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a unique username based on the provided base name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseName">The preferred username</param>
|
||||||
|
/// <returns>A unique username</returns>
|
||||||
|
Task<string> GenerateUniqueUsernameAsync(string baseName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a username already exists
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> IsUsernameExistsAsync(string username);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sanitizes a username to remove invalid characters
|
||||||
|
/// </summary>
|
||||||
|
string SanitizeUsername(string username);
|
||||||
|
}
|
24
DysonNetwork.Shared/Services/IActionLogService.cs
Normal file
24
DysonNetwork.Shared/Services/IActionLogService.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using MagicOnion;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface IActionLogService : IService<IActionLogService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an action log entry
|
||||||
|
/// </summary>
|
||||||
|
void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an action log entry from an HTTP request
|
||||||
|
/// </summary>
|
||||||
|
void CreateActionLogFromRequest(
|
||||||
|
string action,
|
||||||
|
Dictionary<string, object> meta,
|
||||||
|
HttpRequest request,
|
||||||
|
Account? account = null
|
||||||
|
);
|
||||||
|
}
|
30
DysonNetwork.Shared/Services/IMagicSpellService.cs
Normal file
30
DysonNetwork.Shared/Services/IMagicSpellService.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using MagicOnion;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface IMagicSpellService : IService<IMagicSpellService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new magic spell
|
||||||
|
/// </summary>
|
||||||
|
Task<MagicSpell> CreateMagicSpell(
|
||||||
|
Account account,
|
||||||
|
MagicSpellType type,
|
||||||
|
Dictionary<string, object> meta,
|
||||||
|
Instant? expiredAt = null,
|
||||||
|
Instant? affectedAt = null,
|
||||||
|
bool preventRepeat = false
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a magic spell by its token
|
||||||
|
/// </summary>
|
||||||
|
Task<MagicSpell?> GetMagicSpellAsync(string token);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Consumes a magic spell
|
||||||
|
/// </summary>
|
||||||
|
Task ApplyMagicSpell(string token);
|
||||||
|
}
|
23
DysonNetwork.Shared/Services/INotificationService.cs
Normal file
23
DysonNetwork.Shared/Services/INotificationService.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using MagicOnion;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface INotificationService : IService<INotificationService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes a device from push notifications
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">The device ID to unsubscribe</param>
|
||||||
|
Task UnsubscribePushNotifications(string deviceId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes a device to push notifications
|
||||||
|
/// </summary>
|
||||||
|
Task<NotificationPushSubscription> SubscribePushNotification(
|
||||||
|
Account account,
|
||||||
|
NotificationPushProvider provider,
|
||||||
|
string deviceId,
|
||||||
|
string deviceToken
|
||||||
|
);
|
||||||
|
}
|
27
DysonNetwork.Shared/Services/IRelationshipService.cs
Normal file
27
DysonNetwork.Shared/Services/IRelationshipService.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using MagicOnion;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Services;
|
||||||
|
|
||||||
|
public interface IRelationshipService : IService<IRelationshipService>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a relationship exists between two accounts
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a relationship between two accounts
|
||||||
|
/// </summary>
|
||||||
|
Task<Relationship?> GetRelationship(
|
||||||
|
Guid accountId,
|
||||||
|
Guid relatedId,
|
||||||
|
RelationshipStatus? status = null,
|
||||||
|
bool ignoreExpired = false
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new relationship between two accounts
|
||||||
|
/// </summary>
|
||||||
|
Task<Relationship> CreateRelationship(Account sender, Account target, RelationshipStatus status);
|
||||||
|
}
|
60
DysonNetwork.Shared/Startup/ApplicationConfiguration.cs
Normal file
60
DysonNetwork.Shared/Startup/ApplicationConfiguration.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Startup;
|
||||||
|
|
||||||
|
public static class ApplicationConfiguration
|
||||||
|
{
|
||||||
|
public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
app.MapOpenApi();
|
||||||
|
|
||||||
|
app.UseRequestLocalization();
|
||||||
|
|
||||||
|
ConfigureForwardedHeaders(app, configuration);
|
||||||
|
|
||||||
|
app.UseCors(opts =>
|
||||||
|
opts.SetIsOriginAllowed(_ => true)
|
||||||
|
.WithExposedHeaders("*")
|
||||||
|
.WithHeaders()
|
||||||
|
.AllowCredentials()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
);
|
||||||
|
|
||||||
|
app.UseWebSockets();
|
||||||
|
app.UseRateLimiter();
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers().RequireRateLimiting("fixed");
|
||||||
|
app.MapStaticAssets().RequireRateLimiting("fixed");
|
||||||
|
app.MapRazorPages().RequireRateLimiting("fixed");
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureForwardedHeaders(WebApplication app, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var knownProxiesSection = configuration.GetSection("KnownProxies");
|
||||||
|
var forwardedHeadersOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All };
|
||||||
|
|
||||||
|
if (knownProxiesSection.Exists())
|
||||||
|
{
|
||||||
|
var proxyAddresses = knownProxiesSection.Get<string[]>();
|
||||||
|
if (proxyAddresses != null)
|
||||||
|
foreach (var proxy in proxyAddresses)
|
||||||
|
if (IPAddress.TryParse(proxy, out var ipAddress))
|
||||||
|
forwardedHeadersOptions.KnownProxies.Add(ipAddress);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
forwardedHeadersOptions.KnownProxies.Add(IPAddress.Any);
|
||||||
|
forwardedHeadersOptions.KnownProxies.Add(IPAddress.IPv6Any);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseForwardedHeaders(forwardedHeadersOptions);
|
||||||
|
}
|
||||||
|
}
|
21
DysonNetwork.Shared/Startup/KestrelConfiguration.cs
Normal file
21
DysonNetwork.Shared/Startup/KestrelConfiguration.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Startup;
|
||||||
|
|
||||||
|
public static class KestrelConfiguration
|
||||||
|
{
|
||||||
|
public static WebApplicationBuilder ConfigureAppKestrel(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Host.UseContentRoot(Directory.GetCurrentDirectory());
|
||||||
|
builder.WebHost.ConfigureKestrel(options =>
|
||||||
|
{
|
||||||
|
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024;
|
||||||
|
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
|
||||||
|
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
@ -25,9 +25,11 @@
|
|||||||
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
||||||
<PackageReference Include="Livekit.Server.Sdk.Dotnet" Version="1.0.8" />
|
<PackageReference Include="Livekit.Server.Sdk.Dotnet" Version="1.0.8" />
|
||||||
|
<PackageReference Include="MagicOnion.Client" Version="7.0.5" />
|
||||||
|
<PackageReference Include="MagicOnion.Server" Version="7.0.5" />
|
||||||
<PackageReference Include="MailKit" Version="4.11.0" />
|
<PackageReference Include="MailKit" Version="4.11.0" />
|
||||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.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.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@ -70,7 +72,7 @@
|
|||||||
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.9" />
|
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.9" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
|
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
|
||||||
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0" />
|
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.1" />
|
||||||
<PackageReference Include="System.ServiceModel.Syndication" Version="9.0.6" />
|
<PackageReference Include="System.ServiceModel.Syndication" Version="9.0.6" />
|
||||||
<PackageReference Include="tusdotnet" Version="2.8.1" />
|
<PackageReference Include="tusdotnet" Version="2.8.1" />
|
||||||
@ -83,7 +85,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Auth\" />
|
|
||||||
<Folder Include="Discovery\" />
|
<Folder Include="Discovery\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -164,10 +165,6 @@
|
|||||||
<_ContentIncludedByDefault Remove="app\publish\package.json" />
|
<_ContentIncludedByDefault Remove="app\publish\package.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="Protos\auth.proto" GrpcServices="Server" />
|
<Protobuf Include="Protos\auth.proto" GrpcServices="Server" />
|
||||||
<Protobuf Include="Protos\account.proto" GrpcServices="Server" />
|
<Protobuf Include="Protos\account.proto" GrpcServices="Server" />
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using DysonNetwork.Sphere.Permission;
|
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
@ -35,7 +34,6 @@ public static class ApplicationConfiguration
|
|||||||
app.UseRateLimiter();
|
app.UseRateLimiter();
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.UseMiddleware<PermissionMiddleware>();
|
|
||||||
|
|
||||||
app.MapControllers().RequireRateLimiting("fixed");
|
app.MapControllers().RequireRateLimiting("fixed");
|
||||||
app.MapStaticAssets().RequireRateLimiting("fixed");
|
app.MapStaticAssets().RequireRateLimiting("fixed");
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using DysonNetwork.Sphere.Account;
|
|
||||||
using DysonNetwork.Sphere.Activity;
|
using DysonNetwork.Sphere.Activity;
|
||||||
using DysonNetwork.Sphere.Auth;
|
|
||||||
using DysonNetwork.Sphere.Auth.OpenId;
|
|
||||||
using DysonNetwork.Sphere.Chat;
|
using DysonNetwork.Sphere.Chat;
|
||||||
using DysonNetwork.Sphere.Chat.Realtime;
|
using DysonNetwork.Sphere.Chat.Realtime;
|
||||||
using DysonNetwork.Sphere.Connection;
|
using DysonNetwork.Sphere.Connection;
|
||||||
using DysonNetwork.Sphere.Connection.Handlers;
|
using DysonNetwork.Sphere.Connection.Handlers;
|
||||||
using DysonNetwork.Sphere.Email;
|
using DysonNetwork.Sphere.Email;
|
||||||
using DysonNetwork.Sphere.Localization;
|
using DysonNetwork.Sphere.Localization;
|
||||||
using DysonNetwork.Sphere.Permission;
|
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Publisher;
|
using DysonNetwork.Sphere.Publisher;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
@ -25,8 +21,6 @@ using StackExchange.Redis;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Sphere.Auth.OidcProvider.Options;
|
|
||||||
using DysonNetwork.Sphere.Auth.OidcProvider.Services;
|
|
||||||
using DysonNetwork.Sphere.Connection.WebReader;
|
using DysonNetwork.Sphere.Connection.WebReader;
|
||||||
using DysonNetwork.Sphere.Developer;
|
using DysonNetwork.Sphere.Developer;
|
||||||
using DysonNetwork.Sphere.Discovery;
|
using DysonNetwork.Sphere.Discovery;
|
||||||
@ -54,20 +48,6 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
|
|
||||||
// Register OIDC services
|
|
||||||
services.AddScoped<OidcService, GoogleOidcService>();
|
|
||||||
services.AddScoped<OidcService, AppleOidcService>();
|
|
||||||
services.AddScoped<OidcService, GitHubOidcService>();
|
|
||||||
services.AddScoped<OidcService, MicrosoftOidcService>();
|
|
||||||
services.AddScoped<OidcService, DiscordOidcService>();
|
|
||||||
services.AddScoped<OidcService, AfdianOidcService>();
|
|
||||||
services.AddScoped<GoogleOidcService>();
|
|
||||||
services.AddScoped<AppleOidcService>();
|
|
||||||
services.AddScoped<GitHubOidcService>();
|
|
||||||
services.AddScoped<MicrosoftOidcService>();
|
|
||||||
services.AddScoped<DiscordOidcService>();
|
|
||||||
services.AddScoped<AfdianOidcService>();
|
|
||||||
|
|
||||||
services.AddControllers().AddJsonOptions(options =>
|
services.AddControllers().AddJsonOptions(options =>
|
||||||
{
|
{
|
||||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||||
@ -113,12 +93,12 @@ public static class ServiceCollectionExtensions
|
|||||||
{
|
{
|
||||||
services.AddCors();
|
services.AddCors();
|
||||||
services.AddAuthorization();
|
services.AddAuthorization();
|
||||||
services.AddAuthentication(options =>
|
// services.AddAuthentication(options =>
|
||||||
{
|
// {
|
||||||
options.DefaultAuthenticateScheme = AuthConstants.SchemeName;
|
// options.DefaultAuthenticateScheme = AuthConstants.SchemeName;
|
||||||
options.DefaultChallengeScheme = AuthConstants.SchemeName;
|
// options.DefaultChallengeScheme = AuthConstants.SchemeName;
|
||||||
})
|
// })
|
||||||
.AddScheme<DysonTokenAuthOptions, DysonTokenAuthHandler>(AuthConstants.SchemeName, _ => { });
|
// .AddScheme<DysonTokenAuthOptions, DysonTokenAuthHandler>(AuthConstants.SchemeName, _ => { });
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@ -200,22 +180,11 @@ public static class ServiceCollectionExtensions
|
|||||||
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services,
|
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
services.AddScoped<CompactTokenService>();
|
|
||||||
services.AddScoped<RazorViewRenderer>();
|
services.AddScoped<RazorViewRenderer>();
|
||||||
services.Configure<GeoIpOptions>(configuration.GetSection("GeoIP"));
|
services.Configure<GeoIpOptions>(configuration.GetSection("GeoIP"));
|
||||||
services.AddScoped<GeoIpService>();
|
services.AddScoped<GeoIpService>();
|
||||||
services.AddScoped<WebSocketService>();
|
services.AddScoped<WebSocketService>();
|
||||||
services.AddScoped<EmailService>();
|
services.AddScoped<EmailService>();
|
||||||
services.AddScoped<PermissionService>();
|
|
||||||
services.AddScoped<ActionLogService>();
|
|
||||||
services.AddScoped<AccountService>();
|
|
||||||
services.AddScoped<AccountEventService>();
|
|
||||||
services.AddScoped<ActionLogService>();
|
|
||||||
services.AddScoped<RelationshipService>();
|
|
||||||
services.AddScoped<MagicSpellService>();
|
|
||||||
services.AddScoped<NotificationService>();
|
|
||||||
services.AddScoped<AuthService>();
|
|
||||||
services.AddScoped<AccountUsernameService>();
|
|
||||||
services.AddScoped<FileService>();
|
services.AddScoped<FileService>();
|
||||||
services.AddScoped<FileReferenceService>();
|
services.AddScoped<FileReferenceService>();
|
||||||
services.AddScoped<FileReferenceMigrationService>();
|
services.AddScoped<FileReferenceMigrationService>();
|
||||||
@ -238,9 +207,6 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<DiscoveryService>();
|
services.AddScoped<DiscoveryService>();
|
||||||
services.AddScoped<CustomAppService>();
|
services.AddScoped<CustomAppService>();
|
||||||
|
|
||||||
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
|
||||||
services.AddScoped<OidcProviderService>();
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -46,6 +46,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInternalServerError_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003Fb5_003Fc55acdd2_003FInternalServerError_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInternalServerError_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003Fb5_003Fc55acdd2_003FInternalServerError_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIPAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9019acc02f6742e3b19ac2eab79854df3be00_003F58_003F61b957a0_003FIPAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIPAddress_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9019acc02f6742e3b19ac2eab79854df3be00_003F58_003F61b957a0_003FIPAddress_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIServiceCollectionQuartzConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003F67_003Faee36f5b_003FIServiceCollectionQuartzConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIService_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff9c8c2674be647f2bd099567200bfca2ba00_003F29_003F5fd3562b_003FIService_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIStringLocalizerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aa8ac544afb487082402c1fa422910f2e00_003F7f_003F8e728ed6_003FIStringLocalizerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIStringLocalizerFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aa8ac544afb487082402c1fa422910f2e00_003F7f_003F8e728ed6_003FIStringLocalizerFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializerOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5703920a18f94462b4354fab05326e6519a200_003F35_003F8536fc49_003FJsonSerializerOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Reference in New Issue
Block a user