feat: split migration into batches

This commit is contained in:
sam 2024-12-17 21:23:02 +01:00
parent d518cdf739
commit 80385893c7
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
7 changed files with 104 additions and 7 deletions

2
.gitignore vendored
View file

@ -10,3 +10,5 @@ proxy-config.json
docker/config.ini docker/config.ini
docker/proxy-config.json docker/proxy-config.json
docker/frontend.env docker/frontend.env
Foxnouns.DataMigrator/apps.json

View file

@ -0,0 +1,51 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Foxnouns.Backend.Database.Migrations
{
/// <inheritdoc />
[DbContext(typeof(DatabaseContext))]
[Migration("20241217195351_AddFediAppForceRefresh")]
public partial class AddFediAppForceRefresh : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<Dictionary<string, string>>(
name: "localization_params",
table: "notifications",
type: "hstore",
nullable: false,
oldClrType: typeof(Dictionary<string, string>),
oldType: "hstore",
oldNullable: true
);
migrationBuilder.AddColumn<bool>(
name: "force_refresh",
table: "fediverse_applications",
type: "boolean",
nullable: false,
defaultValue: false
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "force_refresh", table: "fediverse_applications");
migrationBuilder.AlterColumn<Dictionary<string, string>>(
name: "localization_params",
table: "notifications",
type: "hstore",
nullable: true,
oldClrType: typeof(Dictionary<string, string>),
oldType: "hstore"
);
}
}
}

View file

@ -216,6 +216,10 @@ namespace Foxnouns.Backend.Database.Migrations
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("domain"); .HasColumnName("domain");
b.Property<bool>("ForceRefresh")
.HasColumnType("boolean")
.HasColumnName("force_refresh");
b.Property<int>("InstanceType") b.Property<int>("InstanceType")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("instance_type"); .HasColumnName("instance_type");
@ -342,6 +346,7 @@ namespace Foxnouns.Backend.Database.Migrations
.HasColumnName("localization_key"); .HasColumnName("localization_key");
b.Property<Dictionary<string, string>>("LocalizationParams") b.Property<Dictionary<string, string>>("LocalizationParams")
.IsRequired()
.HasColumnType("hstore") .HasColumnType("hstore")
.HasColumnName("localization_params"); .HasColumnName("localization_params");
@ -411,7 +416,7 @@ namespace Foxnouns.Backend.Database.Migrations
b.Property<long>("ReporterId") b.Property<long>("ReporterId")
.HasColumnType("bigint") .HasColumnType("bigint")
.HasColumnName("reporter_id"); .HasColumnName("reporter_id");
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("status"); .HasColumnName("status");

View file

@ -20,6 +20,7 @@ public class FediverseApplication : BaseModel
public required string ClientId { get; set; } public required string ClientId { get; set; }
public required string ClientSecret { get; set; } public required string ClientSecret { get; set; }
public required FediverseInstanceType InstanceType { get; set; } public required FediverseInstanceType InstanceType { get; set; }
public bool ForceRefresh { get; set; }
} }
public enum FediverseInstanceType public enum FediverseInstanceType

View file

@ -58,7 +58,7 @@ public partial class FediverseAuthService
) )
{ {
FediverseApplication app = await GetApplicationAsync(instance); FediverseApplication app = await GetApplicationAsync(instance);
return await GenerateAuthUrlAsync(app, forceRefresh, state); return await GenerateAuthUrlAsync(app, forceRefresh || app.ForceRefresh, state);
} }
// thank you, gargron and syuilo, for agreeing on a name for *once* in your lives, // thank you, gargron and syuilo, for agreeing on a name for *once* in your lives,

View file

@ -6,6 +6,7 @@ using Foxnouns.Backend.Extensions;
using Foxnouns.DataMigrator.Models; using Foxnouns.DataMigrator.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Npgsql; using Npgsql;
using Serilog; using Serilog;
using Serilog.Sinks.SystemConsole.Themes; using Serilog.Sinks.SystemConsole.Themes;
@ -22,6 +23,12 @@ internal class Program
.WriteTo.Console(theme: AnsiConsoleTheme.Sixteen) .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen)
.CreateLogger(); .CreateLogger();
var minUserId = new Snowflake(0);
if (args.Length > 0)
minUserId = ulong.Parse(args[0]);
Log.Information("Starting migration from user ID {MinUserId}", minUserId);
Config config = Config config =
new ConfigurationBuilder() new ConfigurationBuilder()
.AddConfiguration() .AddConfiguration()
@ -35,11 +42,30 @@ internal class Program
await context.Database.MigrateAsync(); await context.Database.MigrateAsync();
Log.Information("Migrating applications"); Dictionary<int, Snowflake> appIds;
Dictionary<int, Snowflake> appIds = await MigrateAppsAsync(conn, context); if (minUserId == new Snowflake(0))
{
Log.Information("Migrating applications");
appIds = await MigrateAppsAsync(conn, context);
string appJson = JsonConvert.SerializeObject(appIds);
await File.WriteAllTextAsync("apps.json", appJson);
}
else
{
Log.Information(
"Not the first migration, reading application IDs from {Filename}",
"apps.json"
);
string appJson = await File.ReadAllTextAsync("apps.json");
appIds =
JsonConvert.DeserializeObject<Dictionary<int, Snowflake>>(appJson)
?? throw new Exception("invalid apps.json file");
}
Log.Information("Migrating users"); Log.Information("Migrating users");
List<GoUser> users = await Queries.GetUsersAsync(conn); List<GoUser> users = await Queries.GetUsersAsync(conn, minUserId);
List<GoUserField> userFields = await Queries.GetUserFieldsAsync(conn); List<GoUserField> userFields = await Queries.GetUserFieldsAsync(conn);
List<GoMemberField> memberFields = await Queries.GetMemberFieldsAsync(conn); List<GoMemberField> memberFields = await Queries.GetMemberFieldsAsync(conn);
List<GoPrideFlag> prideFlags = await Queries.GetUserFlagsAsync(conn); List<GoPrideFlag> prideFlags = await Queries.GetUserFlagsAsync(conn);
@ -70,6 +96,12 @@ internal class Program
await context.SaveChangesAsync(); await context.SaveChangesAsync();
Log.Information("Migration complete!"); Log.Information("Migration complete!");
Log.Information(
"Migrated {Count} users, last user was {UserId}. Complete? {Complete}",
users.Count,
users.Last().SnowflakeId,
users.Count != 1000
);
} }
private static async Task<Dictionary<int, Snowflake>> MigrateAppsAsync( private static async Task<Dictionary<int, Snowflake>> MigrateAppsAsync(
@ -92,6 +124,7 @@ internal class Program
ClientId = app.ClientId, ClientId = app.ClientId,
ClientSecret = app.ClientSecret, ClientSecret = app.ClientSecret,
InstanceType = app.TypeToEnum(), InstanceType = app.TypeToEnum(),
ForceRefresh = true,
} }
); );
} }

View file

@ -13,8 +13,13 @@ public static class Queries
public static async Task<List<GoFediverseApp>> GetFediverseAppsAsync(NpgsqlConnection conn) => public static async Task<List<GoFediverseApp>> GetFediverseAppsAsync(NpgsqlConnection conn) =>
(await conn.QueryAsync<GoFediverseApp>("select * from fediverse_apps")).ToList(); (await conn.QueryAsync<GoFediverseApp>("select * from fediverse_apps")).ToList();
public static async Task<List<GoUser>> GetUsersAsync(NpgsqlConnection conn) => public static async Task<List<GoUser>> GetUsersAsync(NpgsqlConnection conn, Snowflake minId) =>
(await conn.QueryAsync<GoUser>("select * from users order by id")).ToList(); (
await conn.QueryAsync<GoUser>(
"select * from users where snowflake_id > @Id order by snowflake_id limit 1000",
new { Id = minId.Value }
)
).ToList();
public static async Task<List<GoUserField>> GetUserFieldsAsync(NpgsqlConnection conn) => public static async Task<List<GoUserField>> GetUserFieldsAsync(NpgsqlConnection conn) =>
(await conn.QueryAsync<GoUserField>("select * from user_fields order by id")).ToList(); (await conn.QueryAsync<GoUserField>("select * from user_fields order by id")).ToList();