♻️ No idea, but errors all gone
This commit is contained in:
@@ -11,18 +11,27 @@ using OtpNet;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using EFCore.BulkExtensions;
|
||||
using MagicOnion.Server;
|
||||
using Grpc.Core;
|
||||
using DysonNetwork.Pass.Account;
|
||||
using DysonNetwork.Pass.Auth.OidcProvider.Services;
|
||||
using DysonNetwork.Pass.Localization;
|
||||
using DysonNetwork.Shared.Localization;
|
||||
using DysonNetwork.Shared.Services;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public class AccountService(
|
||||
AppDatabase db,
|
||||
// MagicSpellService spells,
|
||||
// AccountUsernameService uname,
|
||||
// NotificationService nty,
|
||||
// EmailService mailer,
|
||||
// IStringLocalizer<NotificationResource> localizer,
|
||||
MagicSpellService spells,
|
||||
AccountUsernameService uname,
|
||||
NotificationService nty,
|
||||
// EmailService mailer, // Commented out for now
|
||||
IStringLocalizer<NotificationResource> localizer,
|
||||
ICacheService cache,
|
||||
ILogger<AccountService> logger
|
||||
ILogger<AccountService> logger,
|
||||
AuthService authService,
|
||||
ActionLogService actionLogService,
|
||||
RelationshipService relationshipService
|
||||
) : ServiceBase<IAccountService>, IAccountService
|
||||
{
|
||||
public static void SetCultureInfo(Shared.Models.Account account)
|
||||
@@ -134,15 +143,15 @@ public class AccountService(
|
||||
}
|
||||
else
|
||||
{
|
||||
// var spell = await spells.CreateMagicSpell(
|
||||
// account,
|
||||
// MagicSpellType.AccountActivation,
|
||||
// new Dictionary<string, object>
|
||||
// {
|
||||
// { "contact_method", account.Contacts.First().Content }
|
||||
// }
|
||||
// );
|
||||
// await spells.NotifyMagicSpell(spell, true);
|
||||
var spell = await spells.CreateMagicSpell(
|
||||
account,
|
||||
MagicSpellType.AccountActivation,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "contact_method", account.Contacts.First().Content }
|
||||
}
|
||||
);
|
||||
await spells.NotifyMagicSpell(spell, true);
|
||||
}
|
||||
|
||||
db.Accounts.Add(account);
|
||||
@@ -167,9 +176,7 @@ public class AccountService(
|
||||
? userInfo.DisplayName
|
||||
: $"{userInfo.FirstName} {userInfo.LastName}".Trim();
|
||||
|
||||
// Generate username from email
|
||||
// var username = await uname.GenerateUsernameFromEmailAsync(userInfo.Email);
|
||||
var username = userInfo.Email.Split('@')[0]; // Placeholder
|
||||
var username = await uname.GenerateUsernameFromEmailAsync(userInfo.Email);
|
||||
|
||||
return await CreateAccount(
|
||||
username,
|
||||
@@ -184,28 +191,26 @@ public class AccountService(
|
||||
|
||||
public async Task RequestAccountDeletion(Shared.Models.Account account)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
// var spell = await spells.CreateMagicSpell(
|
||||
// account,
|
||||
// MagicSpellType.AccountRemoval,
|
||||
// new Dictionary<string, object>(),
|
||||
// SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)),
|
||||
// preventRepeat: true
|
||||
// );
|
||||
// await spells.NotifyMagicSpell(spell);
|
||||
var spell = await spells.CreateMagicSpell(
|
||||
account,
|
||||
MagicSpellType.AccountRemoval,
|
||||
new Dictionary<string, object>(),
|
||||
SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)),
|
||||
preventRepeat: true
|
||||
);
|
||||
await spells.NotifyMagicSpell(spell);
|
||||
}
|
||||
|
||||
public async Task RequestPasswordReset(Shared.Models.Account account)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
// var spell = await spells.CreateMagicSpell(
|
||||
// account,
|
||||
// MagicSpellType.AuthPasswordReset,
|
||||
// new Dictionary<string, object>(),
|
||||
// SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)),
|
||||
// preventRepeat: true
|
||||
// );
|
||||
// await spells.NotifyMagicSpell(spell);
|
||||
var spell = await spells.CreateMagicSpell(
|
||||
account,
|
||||
MagicSpellType.AuthPasswordReset,
|
||||
new Dictionary<string, object>(),
|
||||
SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)),
|
||||
preventRepeat: true
|
||||
);
|
||||
await spells.NotifyMagicSpell(spell);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckAuthFactorExists(Shared.Models.Account account, AccountAuthFactorType type)
|
||||
@@ -331,7 +336,6 @@ public class AccountService(
|
||||
{
|
||||
var count = await db.AccountAuthFactors
|
||||
.Where(f => f.AccountId == factor.AccountId)
|
||||
// .If(factor.EnabledAt is not null, q => q.Where(f => f.EnabledAt != null))
|
||||
.CountAsync();
|
||||
if (count <= 1)
|
||||
throw new InvalidOperationException("Deleting this auth factor will cause you have no auth factor.");
|
||||
@@ -357,14 +361,14 @@ public class AccountService(
|
||||
if (await _GetFactorCode(factor) is not null)
|
||||
throw new InvalidOperationException("A factor code has been sent and in active duration.");
|
||||
|
||||
// await nty.SendNotification(
|
||||
// account,
|
||||
// "auth.verification",
|
||||
// localizer["AuthCodeTitle"],
|
||||
// null,
|
||||
// localizer["AuthCodeBody", code],
|
||||
// save: true
|
||||
// );
|
||||
await nty.SendNotification(
|
||||
account,
|
||||
"auth.verification",
|
||||
localizer["AuthCodeTitle"],
|
||||
null,
|
||||
localizer["AuthCodeBody", code],
|
||||
save: true
|
||||
);
|
||||
await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5));
|
||||
break;
|
||||
case AccountAuthFactorType.EmailCode:
|
||||
@@ -399,11 +403,11 @@ public class AccountService(
|
||||
return;
|
||||
}
|
||||
|
||||
// await mailer.SendTemplatedEmailAsync<DysonNetwork.Sphere.Pages.Emails.VerificationEmail, VerificationEmailModel>(
|
||||
// await mailer.SendTemplatedEmailAsync<DysonNetwork.Pass.Pages.Emails.VerificationEmail, DysonNetwork.Pass.Pages.Emails.VerificationEmailModel>(
|
||||
// account.Nick,
|
||||
// contact.Content,
|
||||
// localizer["VerificationEmail"],
|
||||
// new VerificationEmailModel
|
||||
// new DysonNetwork.Pass.Pages.Emails.VerificationEmailModel
|
||||
// {
|
||||
// Name = account.Name,
|
||||
// Code = code
|
||||
@@ -456,7 +460,7 @@ public class AccountService(
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Session> UpdateSessionLabel(Shared.Models.Account account, Guid sessionId, string label)
|
||||
public async Task<Shared.Models.Session> UpdateSessionLabel(Shared.Models.Account account, Guid sessionId, string label)
|
||||
{
|
||||
var session = await db.AuthSessions
|
||||
.Include(s => s.Challenge)
|
||||
@@ -493,7 +497,7 @@ public class AccountService(
|
||||
.ToListAsync();
|
||||
|
||||
if (session.Challenge.DeviceId is not null)
|
||||
// await nty.UnsubscribePushNotifications(session.Challenge.DeviceId);
|
||||
await nty.UnsubscribePushNotifications(session.Challenge.DeviceId);
|
||||
|
||||
// The current session should be included in the sessions' list
|
||||
await db.AuthSessions
|
||||
@@ -522,15 +526,14 @@ public class AccountService(
|
||||
|
||||
public async Task VerifyContactMethod(Shared.Models.Account account, AccountContact contact)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
// var spell = await spells.CreateMagicSpell(
|
||||
// account,
|
||||
// MagicSpellType.ContactVerification,
|
||||
// new Dictionary<string, object> { { "contact_method", contact.Content } },
|
||||
// expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)),
|
||||
// preventRepeat: true
|
||||
// );
|
||||
// await spells.NotifyMagicSpell(spell);
|
||||
var spell = await spells.CreateMagicSpell(
|
||||
account,
|
||||
MagicSpellType.ContactVerification,
|
||||
new Dictionary<string, object> { { "contact_method", contact.Content } },
|
||||
expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)),
|
||||
preventRepeat: true
|
||||
);
|
||||
await spells.NotifyMagicSpell(spell);
|
||||
}
|
||||
|
||||
public async Task<AccountContact> SetContactMethodPrimary(Shared.Models.Account account, AccountContact contact)
|
||||
@@ -614,7 +617,7 @@ public class AccountService(
|
||||
try
|
||||
{
|
||||
var badge = await db.AccountBadges
|
||||
.Where(b => b.AccountId == account.Id && b.Id == badgeId)
|
||||
.Where(b => b.AccountId == account.Id && b.Id != badgeId)
|
||||
.OrderByDescending(b => b.CreatedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
if (badge is null) throw new InvalidOperationException("Badge was not found.");
|
||||
@@ -657,4 +660,246 @@ public class AccountService(
|
||||
await db.BulkInsertAsync(newProfiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Shared.Models.Account?> GetAccountById(Guid accountId, bool withProfile = false)
|
||||
{
|
||||
return await db.Accounts
|
||||
.Where(a => a.Id == accountId)
|
||||
.If(withProfile, q => q.Include(a => a.Profile))
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<Profile?> GetAccountProfile(Guid accountId)
|
||||
{
|
||||
return await db.AccountProfiles.FirstOrDefaultAsync(p => p.AccountId == accountId);
|
||||
}
|
||||
|
||||
public async Task<Challenge?> GetAuthChallenge(Guid challengeId)
|
||||
{
|
||||
return await db.AuthChallenges.FindAsync(challengeId);
|
||||
}
|
||||
|
||||
public async Task<Challenge?> GetAuthChallenge(Guid accountId, string? ipAddress, string? userAgent, Instant now)
|
||||
{
|
||||
return await db.AuthChallenges
|
||||
.Where(e => e.AccountId == accountId)
|
||||
.Where(e => e.IpAddress == ipAddress)
|
||||
.Where(e => e.UserAgent == userAgent)
|
||||
.Where(e => e.StepRemain > 0)
|
||||
.Where(e => e.ExpiredAt != null && now < e.ExpiredAt)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<Challenge> CreateAuthChallenge(Challenge challenge)
|
||||
{
|
||||
db.AuthChallenges.Add(challenge);
|
||||
await db.SaveChangesAsync();
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public async Task<AccountAuthFactor?> GetAccountAuthFactor(Guid factorId, Guid accountId)
|
||||
{
|
||||
return await db.AccountAuthFactors.FirstOrDefaultAsync(f => f.Id == factorId && f.AccountId == accountId);
|
||||
}
|
||||
|
||||
public async Task<List<AccountAuthFactor>> GetAccountAuthFactors(Guid accountId)
|
||||
{
|
||||
return await db.AccountAuthFactors
|
||||
.Where(e => e.AccountId == accountId)
|
||||
.Where(e => e.EnabledAt != null && e.Trustworthy >= 1)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Session?> GetAuthSession(Guid sessionId)
|
||||
{
|
||||
return await db.AuthSessions.FindAsync(sessionId);
|
||||
}
|
||||
|
||||
public async Task<MagicSpell?> GetMagicSpell(Guid spellId)
|
||||
{
|
||||
return await db.MagicSpells.FindAsync(spellId);
|
||||
}
|
||||
|
||||
public async Task<AbuseReport?> GetAbuseReport(Guid reportId)
|
||||
{
|
||||
return await db.AbuseReports.FindAsync(reportId);
|
||||
}
|
||||
|
||||
public async Task<AbuseReport> CreateAbuseReport(string resourceIdentifier, AbuseReportType type, string reason, Guid accountId)
|
||||
{
|
||||
var existingReport = await db.AbuseReports
|
||||
.Where(r => r.ResourceIdentifier == resourceIdentifier &&
|
||||
r.AccountId == accountId &&
|
||||
r.DeletedAt == null)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (existingReport != null)
|
||||
{
|
||||
throw new InvalidOperationException("You have already reported this content.");
|
||||
}
|
||||
|
||||
var report = new AbuseReport
|
||||
{
|
||||
ResourceIdentifier = resourceIdentifier,
|
||||
Type = type,
|
||||
Reason = reason,
|
||||
AccountId = accountId
|
||||
};
|
||||
|
||||
db.AbuseReports.Add(report);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
logger.LogInformation("New abuse report created: {ReportId} for resource {ResourceId}",
|
||||
report.Id, resourceIdentifier);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
public async Task<int> CountAbuseReports(bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<int> CountUserAbuseReports(Guid accountId, bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => r.AccountId == accountId)
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<List<AbuseReport>> GetAbuseReports(int skip = 0, int take = 20, bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.Include(r => r.Account)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<AbuseReport>> GetUserAbuseReports(Guid accountId, int skip = 0, int take = 20, bool includeResolved = false)
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => r.AccountId == accountId)
|
||||
.Where(r => includeResolved || r.ResolvedAt == null)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AbuseReport> ResolveAbuseReport(Guid id, string resolution)
|
||||
{
|
||||
var report = await db.AbuseReports.FindAsync(id);
|
||||
if (report == null)
|
||||
{
|
||||
throw new KeyNotFoundException("Report not found");
|
||||
}
|
||||
|
||||
report.ResolvedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
report.Resolution = resolution;
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return report;
|
||||
}
|
||||
|
||||
public async Task<int> GetPendingAbuseReportsCount()
|
||||
{
|
||||
return await db.AbuseReports
|
||||
.Where(r => r.ResolvedAt == null)
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasRelationshipWithStatus(Guid accountId1, Guid accountId2, Shared.Models.RelationshipStatus status)
|
||||
{
|
||||
return await db.AccountRelationships.AnyAsync(r =>
|
||||
(r.AccountId == accountId1 && r.RelatedId == accountId2 && r.Status == status) ||
|
||||
(r.AccountId == accountId2 && r.RelatedId == accountId1 && r.Status == status)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, Shared.Models.Status>> GetStatuses(List<Guid> accountIds)
|
||||
{
|
||||
return await db.AccountStatuses
|
||||
.Where(s => accountIds.Contains(s.AccountId))
|
||||
.GroupBy(s => s.AccountId)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.OrderByDescending(s => s.CreatedAt).First());
|
||||
}
|
||||
|
||||
public async Task SendNotification(Shared.Models.Account account, string topic, string title, string? subtitle, string body, string? actionUri = null)
|
||||
{
|
||||
await nty.SendNotification(account, topic, title, subtitle, body, actionUri: actionUri);
|
||||
}
|
||||
|
||||
public async Task<List<Shared.Models.Account>> ListAccountFriends(Shared.Models.Account account)
|
||||
{
|
||||
return await relationshipService.ListAccountFriends(account);
|
||||
}
|
||||
|
||||
public string CreateToken(Shared.Models.Session session)
|
||||
{
|
||||
return authService.CreateToken(session);
|
||||
}
|
||||
|
||||
public string GetAuthCookieTokenName()
|
||||
{
|
||||
return AuthConstants.CookieTokenName;
|
||||
}
|
||||
|
||||
public async Task<ActionLog> CreateActionLogFromRequest(string type, Dictionary<string, object> meta, string? ipAddress, string? userAgent, Shared.Models.Account? account = null)
|
||||
{
|
||||
return await actionLogService.CreateActionLogFromRequest(type, meta, ipAddress, userAgent, account);
|
||||
}
|
||||
|
||||
public async Task<Challenge> UpdateAuthChallenge(Challenge challenge)
|
||||
{
|
||||
db.AuthChallenges.Update(challenge);
|
||||
await db.SaveChangesAsync();
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public async Task<Session> CreateSession(Instant lastGrantedAt, Instant expiredAt, Shared.Models.Account account, Challenge challenge)
|
||||
{
|
||||
var session = new Session
|
||||
{
|
||||
LastGrantedAt = lastGrantedAt,
|
||||
ExpiredAt = expiredAt,
|
||||
Account = account,
|
||||
Challenge = challenge,
|
||||
};
|
||||
db.AuthSessions.Add(session);
|
||||
await db.SaveChangesAsync();
|
||||
return session;
|
||||
}
|
||||
|
||||
public async Task UpdateSessionLastGrantedAt(Guid sessionId, Instant lastGrantedAt)
|
||||
{
|
||||
await db.AuthSessions
|
||||
.Where(s => s.Id == sessionId)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.LastGrantedAt, lastGrantedAt));
|
||||
}
|
||||
|
||||
public async Task UpdateAccountProfileLastSeenAt(Guid accountId, Instant lastSeenAt)
|
||||
{
|
||||
await db.AccountProfiles
|
||||
.Where(a => a.AccountId == accountId)
|
||||
.ExecuteUpdateAsync(a => a.SetProperty(x => x.LastSeenAt, lastSeenAt));
|
||||
}
|
||||
|
||||
public async Task<List<Shared.Models.Account>> SearchAccountsAsync(string searchTerm)
|
||||
{
|
||||
return await db.Accounts
|
||||
.Where(a => EF.Functions.ILike(a.Name, $"%{searchTerm}%"))
|
||||
.OrderBy(a => a.Name)
|
||||
.Take(10)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user