Add whitelisting service + update the jwt valid audience
This commit is contained in:
1602
src/Managing.Infrastructure.Database/Migrations/20251107122139_AddWhitelistAccountsTable.Designer.cs
generated
Normal file
1602
src/Managing.Infrastructure.Database/Migrations/20251107122139_AddWhitelistAccountsTable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Managing.Infrastructure.Databases.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddWhitelistAccountsTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WhitelistAccounts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
PrivyId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
PrivyCreationDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
EmbeddedWallet = table.Column<string>(type: "character varying(42)", maxLength: 42, nullable: false),
|
||||
ExternalEthereumAccount = table.Column<string>(type: "character varying(42)", maxLength: 42, nullable: true),
|
||||
TwitterAccount = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
IsWhitelisted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WhitelistAccounts", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WhitelistAccounts_CreatedAt",
|
||||
table: "WhitelistAccounts",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WhitelistAccounts_EmbeddedWallet",
|
||||
table: "WhitelistAccounts",
|
||||
column: "EmbeddedWallet",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WhitelistAccounts_ExternalEthereumAccount",
|
||||
table: "WhitelistAccounts",
|
||||
column: "ExternalEthereumAccount");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WhitelistAccounts_PrivyId",
|
||||
table: "WhitelistAccounts",
|
||||
column: "PrivyId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WhitelistAccounts_TwitterAccount",
|
||||
table: "WhitelistAccounts",
|
||||
column: "TwitterAccount");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "WhitelistAccounts");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1325,6 +1325,63 @@ namespace Managing.Infrastructure.Databases.Migrations
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WhitelistAccountEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("EmbeddedWallet")
|
||||
.IsRequired()
|
||||
.HasMaxLength(42)
|
||||
.HasColumnType("character varying(42)");
|
||||
|
||||
b.Property<string>("ExternalEthereumAccount")
|
||||
.HasMaxLength(42)
|
||||
.HasColumnType("character varying(42)");
|
||||
|
||||
b.Property<bool>("IsWhitelisted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<DateTime>("PrivyCreationDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("PrivyId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("TwitterAccount")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("EmbeddedWallet")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ExternalEthereumAccount");
|
||||
|
||||
b.HasIndex("PrivyId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("TwitterAccount");
|
||||
|
||||
b.ToTable("WhitelistAccounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
|
||||
|
||||
[Table("WhitelistAccounts")]
|
||||
public class WhitelistAccountEntity
|
||||
{
|
||||
[Key] public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(255)]
|
||||
public required string PrivyId { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime PrivyCreationDate { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(42)]
|
||||
public required string EmbeddedWallet { get; set; }
|
||||
|
||||
[MaxLength(42)]
|
||||
public string? ExternalEthereumAccount { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string? TwitterAccount { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool IsWhitelisted { get; set; } = false;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public class ManagingDbContext : DbContext
|
||||
|
||||
public DbSet<SynthMinersLeaderboardEntity> SynthMinersLeaderboards { get; set; }
|
||||
public DbSet<SynthPredictionEntity> SynthPredictions { get; set; }
|
||||
public DbSet<WhitelistAccountEntity> WhitelistAccounts { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -573,6 +574,29 @@ public class ManagingDbContext : DbContext
|
||||
entity.HasIndex(e => e.CacheKey).IsUnique();
|
||||
});
|
||||
|
||||
// Configure WhitelistAccount entity
|
||||
modelBuilder.Entity<WhitelistAccountEntity>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.PrivyId).IsRequired().HasMaxLength(255);
|
||||
entity.Property(e => e.PrivyCreationDate).IsRequired();
|
||||
entity.Property(e => e.EmbeddedWallet).IsRequired().HasMaxLength(42);
|
||||
entity.Property(e => e.ExternalEthereumAccount).HasMaxLength(42);
|
||||
entity.Property(e => e.TwitterAccount).HasMaxLength(255);
|
||||
entity.Property(e => e.IsWhitelisted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
entity.Property(e => e.CreatedAt).IsRequired();
|
||||
entity.Property(e => e.UpdatedAt);
|
||||
|
||||
// Create indexes for search performance
|
||||
entity.HasIndex(e => e.PrivyId).IsUnique();
|
||||
entity.HasIndex(e => e.EmbeddedWallet).IsUnique();
|
||||
entity.HasIndex(e => e.ExternalEthereumAccount);
|
||||
entity.HasIndex(e => e.TwitterAccount);
|
||||
entity.HasIndex(e => e.CreatedAt);
|
||||
});
|
||||
|
||||
// Configure AgentSummary entity
|
||||
modelBuilder.Entity<AgentSummaryEntity>(entity =>
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Domain.Whitelist;
|
||||
using Managing.Domain.Workers;
|
||||
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
||||
using Newtonsoft.Json;
|
||||
@@ -992,6 +993,51 @@ public static class PostgreSqlMappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region WhitelistAccount Mappings
|
||||
|
||||
public static WhitelistAccount Map(WhitelistAccountEntity entity)
|
||||
{
|
||||
if (entity == null) return null;
|
||||
|
||||
return new WhitelistAccount
|
||||
{
|
||||
Id = entity.Id,
|
||||
PrivyId = entity.PrivyId,
|
||||
PrivyCreationDate = entity.PrivyCreationDate,
|
||||
EmbeddedWallet = entity.EmbeddedWallet,
|
||||
ExternalEthereumAccount = entity.ExternalEthereumAccount,
|
||||
TwitterAccount = entity.TwitterAccount,
|
||||
IsWhitelisted = entity.IsWhitelisted,
|
||||
CreatedAt = entity.CreatedAt,
|
||||
UpdatedAt = entity.UpdatedAt
|
||||
};
|
||||
}
|
||||
|
||||
public static WhitelistAccountEntity Map(WhitelistAccount whitelistAccount)
|
||||
{
|
||||
if (whitelistAccount == null) return null;
|
||||
|
||||
return new WhitelistAccountEntity
|
||||
{
|
||||
Id = whitelistAccount.Id,
|
||||
PrivyId = whitelistAccount.PrivyId,
|
||||
PrivyCreationDate = whitelistAccount.PrivyCreationDate,
|
||||
EmbeddedWallet = whitelistAccount.EmbeddedWallet,
|
||||
ExternalEthereumAccount = whitelistAccount.ExternalEthereumAccount,
|
||||
TwitterAccount = whitelistAccount.TwitterAccount,
|
||||
IsWhitelisted = whitelistAccount.IsWhitelisted,
|
||||
CreatedAt = whitelistAccount.CreatedAt,
|
||||
UpdatedAt = whitelistAccount.UpdatedAt
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<WhitelistAccount> Map(IEnumerable<WhitelistAccountEntity> entities)
|
||||
{
|
||||
return entities?.Select(Map) ?? Enumerable.Empty<WhitelistAccount>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static int? ExtractBundleIndex(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return null;
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Domain.Whitelist;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.PostgreSql;
|
||||
|
||||
public class PostgreSqlWhitelistRepository : BaseRepositoryWithLogging, IWhitelistRepository
|
||||
{
|
||||
public PostgreSqlWhitelistRepository(
|
||||
ManagingDbContext context,
|
||||
ILogger<SqlQueryLogger> logger,
|
||||
SentrySqlMonitoringService sentryMonitoringService)
|
||||
: base(context, logger, sentryMonitoringService)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<WhitelistAccount> Accounts, int TotalCount)> GetPaginatedAsync(
|
||||
int pageNumber,
|
||||
int pageSize,
|
||||
string? searchExternalEthereumAccount = null,
|
||||
string? searchTwitterAccount = null)
|
||||
{
|
||||
return await ExecuteWithLoggingAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
var query = _context.WhitelistAccounts.AsNoTracking().AsQueryable();
|
||||
|
||||
// Apply search filters
|
||||
if (!string.IsNullOrWhiteSpace(searchExternalEthereumAccount))
|
||||
{
|
||||
var searchTerm = searchExternalEthereumAccount.Trim().ToLower();
|
||||
query = query.Where(e =>
|
||||
e.ExternalEthereumAccount != null &&
|
||||
e.ExternalEthereumAccount.ToLower().Contains(searchTerm));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchTwitterAccount))
|
||||
{
|
||||
var searchTerm = searchTwitterAccount.Trim().ToLower();
|
||||
query = query.Where(e =>
|
||||
e.TwitterAccount != null &&
|
||||
e.TwitterAccount.ToLower().Contains(searchTerm));
|
||||
}
|
||||
|
||||
// Get total count before pagination
|
||||
var totalCount = await query.CountAsync().ConfigureAwait(false);
|
||||
|
||||
// Apply pagination and ordering
|
||||
var entities = await query
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var accounts = entities.Select(PostgreSqlMappers.Map);
|
||||
|
||||
return (accounts, totalCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}, nameof(GetPaginatedAsync),
|
||||
("pageNumber", pageNumber.ToString()),
|
||||
("pageSize", pageSize.ToString()));
|
||||
}
|
||||
|
||||
public async Task<int> SetIsWhitelistedAsync(IEnumerable<int> accountIds, bool isWhitelisted)
|
||||
{
|
||||
return await ExecuteWithLoggingAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
var idsList = accountIds.ToList();
|
||||
if (!idsList.Any())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var entities = await _context.WhitelistAccounts
|
||||
.AsTracking()
|
||||
.Where(e => idsList.Contains(e.Id))
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
entity.IsWhitelisted = isWhitelisted;
|
||||
entity.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
var updatedCount = await _context.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return updatedCount;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}, nameof(SetIsWhitelistedAsync),
|
||||
("accountIds", string.Join(",", accountIds)),
|
||||
("isWhitelisted", isWhitelisted.ToString()));
|
||||
}
|
||||
|
||||
public async Task<WhitelistAccount?> GetByIdAsync(int id)
|
||||
{
|
||||
return await ExecuteWithLoggingAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
var entity = await _context.WhitelistAccounts
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(e => e.Id == id)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return entity != null ? PostgreSqlMappers.Map(entity) : null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}, nameof(GetByIdAsync), ("id", id.ToString()));
|
||||
}
|
||||
|
||||
public async Task<WhitelistAccount?> GetByPrivyIdAsync(string privyId)
|
||||
{
|
||||
return await ExecuteWithLoggingAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
var entity = await _context.WhitelistAccounts
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(e => e.PrivyId == privyId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return entity != null ? PostgreSqlMappers.Map(entity) : null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}, nameof(GetByPrivyIdAsync), ("privyId", privyId));
|
||||
}
|
||||
|
||||
public async Task<WhitelistAccount?> GetByEmbeddedWalletAsync(string embeddedWallet)
|
||||
{
|
||||
return await ExecuteWithLoggingAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
var entity = await _context.WhitelistAccounts
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(e => e.EmbeddedWallet.ToLower() == embeddedWallet.ToLower())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return entity != null ? PostgreSqlMappers.Map(entity) : null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}, nameof(GetByEmbeddedWalletAsync), ("embeddedWallet", embeddedWallet));
|
||||
}
|
||||
|
||||
public async Task<WhitelistAccount> CreateOrUpdateAsync(WhitelistAccount whitelistAccount)
|
||||
{
|
||||
return await ExecuteWithLoggingAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context);
|
||||
|
||||
// Check if account exists by PrivyId or EmbeddedWallet
|
||||
var existing = await _context.WhitelistAccounts
|
||||
.AsTracking()
|
||||
.FirstOrDefaultAsync(e =>
|
||||
e.PrivyId == whitelistAccount.PrivyId ||
|
||||
e.EmbeddedWallet.ToLower() == whitelistAccount.EmbeddedWallet.ToLower())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Update existing account
|
||||
existing.PrivyId = whitelistAccount.PrivyId;
|
||||
existing.PrivyCreationDate = whitelistAccount.PrivyCreationDate;
|
||||
existing.EmbeddedWallet = whitelistAccount.EmbeddedWallet;
|
||||
|
||||
// Only update if new value is provided
|
||||
if (!string.IsNullOrWhiteSpace(whitelistAccount.ExternalEthereumAccount))
|
||||
{
|
||||
existing.ExternalEthereumAccount = whitelistAccount.ExternalEthereumAccount;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(whitelistAccount.TwitterAccount))
|
||||
{
|
||||
existing.TwitterAccount = whitelistAccount.TwitterAccount;
|
||||
}
|
||||
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return PostgreSqlMappers.Map(existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new account
|
||||
var entity = PostgreSqlMappers.Map(whitelistAccount);
|
||||
entity.CreatedAt = DateTime.UtcNow;
|
||||
|
||||
_context.WhitelistAccounts.Add(entity);
|
||||
await _context.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return PostgreSqlMappers.Map(entity);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context);
|
||||
}
|
||||
}, nameof(CreateOrUpdateAsync), ("privyId", whitelistAccount.PrivyId));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user