feat: split migration into batches
This commit is contained in:
parent
d518cdf739
commit
80385893c7
7 changed files with 104 additions and 7 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue