♻️ No idea, but errors all gone

This commit is contained in:
2025-07-08 23:55:31 +08:00
parent 2c67472894
commit 63b2b989ba
74 changed files with 1551 additions and 1100 deletions

View File

@@ -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();
}
}