feat(server): worlds backup downloading

This commit is contained in:
CyberL1 2024-06-30 18:31:27 +02:00
parent 83f4a59707
commit 07ae7c6777
9 changed files with 708 additions and 10 deletions

View File

@ -5,10 +5,13 @@ namespace Minecraft_Realms_Emulator.Entities
public class Backup
{
public int Id { get; set; }
public World World { get; set; }
public string BackupId { get; set; }
public Slot Slot { get; set; } = null!;
public string BackupId { get; set; } = null!;
public long LastModifiedDate { get; set; }
public int Size { get; set; }
public JsonDocument Metadata { get; set; }
public JsonDocument Metadata { get; set; } = null!;
public string DownloadUrl { get; set; } = null!;
public string? ResourcePackUrl { get; set; } = null!;
public string? ResourcePackHash { get; set; } = null!;
}
}

View File

@ -0,0 +1,14 @@
namespace Minecraft_Realms_Emulator.Middlewares
{
public class RouteLoggingMiddleware(RequestDelegate next)
{
private readonly RequestDelegate _next = next;
public async Task Invoke(HttpContext httpContext)
{
Console.WriteLine($"{httpContext.Request.Method} {httpContext.Request.Path}{httpContext.Request.QueryString}");
await _next(httpContext);
}
}
}

View File

@ -0,0 +1,512 @@
// <auto-generated />
using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Minecraft_Realms_Emulator.Data;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Minecraft_Realms_Emulator.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20240630160411_Backup_Slots")]
partial class Backup_Slots
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Backup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("BackupId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("DownloadUrl")
.IsRequired()
.HasColumnType("text");
b.Property<long>("LastModifiedDate")
.HasColumnType("bigint");
b.Property<JsonDocument>("Metadata")
.IsRequired()
.HasColumnType("jsonb");
b.Property<string>("ResourcePackHash")
.HasColumnType("text");
b.Property<string>("ResourcePackUrl")
.HasColumnType("text");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<int>("SlotId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("SlotId");
b.ToTable("Backups");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Configuration", b =>
{
b.Property<string>("Key")
.HasColumnType("text");
b.Property<object>("Value")
.IsRequired()
.HasColumnType("jsonb");
b.HasKey("Key");
b.ToTable("Configuration");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Connection", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Address")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("PendingUpdate")
.HasColumnType("boolean");
b.Property<int>("WorldId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("WorldId");
b.ToTable("Connections");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Invite", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("InvitationId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("RecipeintUUID")
.IsRequired()
.HasColumnType("text");
b.Property<int>("WorldId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("WorldId");
b.ToTable("Invites");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Notification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<JsonDocument>("ButtonText")
.HasColumnType("jsonb");
b.Property<bool>("Dismissable")
.HasColumnType("boolean");
b.Property<string>("Image")
.HasColumnType("text");
b.Property<JsonDocument>("Message")
.IsRequired()
.HasColumnType("jsonb");
b.Property<string>("NotificationUuid")
.IsRequired()
.HasColumnType("text");
b.Property<JsonDocument>("Title")
.HasColumnType("jsonb");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<JsonDocument>("UrlButton")
.HasColumnType("jsonb");
b.HasKey("Id");
b.ToTable("Notifications");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Player", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("Accepted")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Online")
.HasColumnType("boolean");
b.Property<bool>("Operator")
.HasColumnType("boolean");
b.Property<string>("Permission")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Uuid")
.IsRequired()
.HasColumnType("text");
b.Property<int>("WorldId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("WorldId");
b.ToTable("Players");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.SeenNotification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("NotificationUUID")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PlayerUUID")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("SeenNotifications");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Slot", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("CommandBlocks")
.HasColumnType("boolean");
b.Property<int>("Difficulty")
.HasColumnType("integer");
b.Property<bool>("ForceGameMode")
.HasColumnType("boolean");
b.Property<int>("GameMode")
.HasColumnType("integer");
b.Property<bool>("Pvp")
.HasColumnType("boolean");
b.Property<int>("SlotId")
.HasColumnType("integer");
b.Property<string>("SlotName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("SpawnAnimals")
.HasColumnType("boolean");
b.Property<bool>("SpawnMonsters")
.HasColumnType("boolean");
b.Property<bool>("SpawnNPCs")
.HasColumnType("boolean");
b.Property<int>("SpawnProtection")
.HasColumnType("integer");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("text");
b.Property<int>("WorldId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("WorldId");
b.ToTable("Slots");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("StartDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("SubscriptionType")
.IsRequired()
.HasColumnType("text");
b.Property<int>("WorldId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("WorldId")
.IsUnique();
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Template", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Author")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Image")
.HasColumnType("text");
b.Property<string>("Link")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("RecommendedPlayers")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Trailer")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Templates");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.World", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ActiveSlot")
.HasColumnType("integer");
b.Property<int>("MaxPlayers")
.HasColumnType("integer");
b.Property<bool>("Member")
.HasColumnType("boolean");
b.Property<int?>("MinigameId")
.HasColumnType("integer");
b.Property<string>("Motd")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Owner")
.HasColumnType("text");
b.Property<string>("OwnerUUID")
.HasColumnType("text");
b.Property<int?>("ParentWorldId")
.HasColumnType("integer");
b.Property<string>("State")
.IsRequired()
.HasColumnType("text");
b.Property<string>("WorldType")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("MinigameId");
b.HasIndex("ParentWorldId");
b.ToTable("Worlds");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Backup", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.Slot", "Slot")
.WithMany()
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Slot");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Connection", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "World")
.WithMany()
.HasForeignKey("WorldId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("World");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Invite", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "World")
.WithMany()
.HasForeignKey("WorldId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("World");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Player", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "World")
.WithMany("Players")
.HasForeignKey("WorldId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("World");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Slot", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "World")
.WithMany("Slots")
.HasForeignKey("WorldId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("World");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Subscription", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "World")
.WithOne("Subscription")
.HasForeignKey("Minecraft_Realms_Emulator.Entities.Subscription", "WorldId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("World");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.World", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.Template", "Minigame")
.WithMany()
.HasForeignKey("MinigameId");
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "ParentWorld")
.WithMany()
.HasForeignKey("ParentWorldId");
b.Navigation("Minigame");
b.Navigation("ParentWorld");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.World", b =>
{
b.Navigation("Players");
b.Navigation("Slots");
b.Navigation("Subscription");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,93 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Minecraft_Realms_Emulator.Migrations
{
/// <inheritdoc />
public partial class Backup_Slots : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Backups_Worlds_WorldId",
table: "Backups");
migrationBuilder.RenameColumn(
name: "WorldId",
table: "Backups",
newName: "SlotId");
migrationBuilder.RenameIndex(
name: "IX_Backups_WorldId",
table: "Backups",
newName: "IX_Backups_SlotId");
migrationBuilder.AddColumn<string>(
name: "DownloadUrl",
table: "Backups",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "ResourcePackHash",
table: "Backups",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ResourcePackUrl",
table: "Backups",
type: "text",
nullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Backups_Slots_SlotId",
table: "Backups",
column: "SlotId",
principalTable: "Slots",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Backups_Slots_SlotId",
table: "Backups");
migrationBuilder.DropColumn(
name: "DownloadUrl",
table: "Backups");
migrationBuilder.DropColumn(
name: "ResourcePackHash",
table: "Backups");
migrationBuilder.DropColumn(
name: "ResourcePackUrl",
table: "Backups");
migrationBuilder.RenameColumn(
name: "SlotId",
table: "Backups",
newName: "WorldId");
migrationBuilder.RenameIndex(
name: "IX_Backups_SlotId",
table: "Backups",
newName: "IX_Backups_WorldId");
migrationBuilder.AddForeignKey(
name: "FK_Backups_Worlds_WorldId",
table: "Backups",
column: "WorldId",
principalTable: "Worlds",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -35,6 +35,10 @@ namespace Minecraft_Realms_Emulator.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<string>("DownloadUrl")
.IsRequired()
.HasColumnType("text");
b.Property<long>("LastModifiedDate")
.HasColumnType("bigint");
@ -42,15 +46,21 @@ namespace Minecraft_Realms_Emulator.Migrations
.IsRequired()
.HasColumnType("jsonb");
b.Property<string>("ResourcePackHash")
.HasColumnType("text");
b.Property<string>("ResourcePackUrl")
.HasColumnType("text");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<int>("WorldId")
b.Property<int>("SlotId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("WorldId");
b.HasIndex("SlotId");
b.ToTable("Backups");
});
@ -406,13 +416,13 @@ namespace Minecraft_Realms_Emulator.Migrations
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Backup", b =>
{
b.HasOne("Minecraft_Realms_Emulator.Entities.World", "World")
b.HasOne("Minecraft_Realms_Emulator.Entities.Slot", "Slot")
.WithMany()
.HasForeignKey("WorldId")
.HasForeignKey("SlotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("World");
b.Navigation("Slot");
});
modelBuilder.Entity("Minecraft_Realms_Emulator.Entities.Connection", b =>

View File

@ -784,7 +784,7 @@ namespace Minecraft_Realms_Emulator.Modes.External
[CheckRealmOwner]
public async Task<ActionResult<BackupsResponse>> GetBackups(int wId)
{
var backups = await _context.Backups.Where(b => b.World.Id == wId).ToListAsync();
var backups = await _context.Backups.Where(b => b.Slot.World.Id == wId).ToListAsync();
BackupsResponse worldBackups = new()
{
@ -794,6 +794,34 @@ namespace Minecraft_Realms_Emulator.Modes.External
return Ok(worldBackups);
}
[HttpGet("{wId}/slot/{sId}/download")]
[CheckForWorld]
[CheckRealmOwner]
public ActionResult<BackupDownloadResponse> GetBackup(int wId, int sId)
{
Backup backup = _context.Backups.Include(b => b.Slot).FirstOrDefault(b => b.Slot.World.Id == wId && b.Slot.Id == sId);
if (backup == null)
{
ErrorResponse errorResponse = new()
{
ErrorCode = 404,
ErrorMsg = "No backup found"
};
return NotFound(errorResponse);
}
BackupDownloadResponse backupDownloadResponse = new()
{
DownloadLink = backup.DownloadUrl,
ResourcePackUrl = backup.ResourcePackUrl,
ResourcePackHash = backup.ResourcePackHash,
};
return Ok(backupDownloadResponse);
}
[HttpGet("v1/{wId}/join/pc")]
public ActionResult<Connection> Join(int wId)
{

View File

@ -855,7 +855,7 @@ namespace Minecraft_Realms_Emulator.Modes.Realms.Controllers
[CheckRealmOwner]
public async Task<ActionResult<BackupsResponse>> GetBackups(int wId)
{
var backups = await _context.Backups.Where(b => b.World.Id == wId).ToListAsync();
var backups = await _context.Backups.Where(b => b.Slot.World.Id == wId).ToListAsync();
BackupsResponse worldBackups = new()
{
@ -865,6 +865,34 @@ namespace Minecraft_Realms_Emulator.Modes.Realms.Controllers
return Ok(worldBackups);
}
[HttpGet("{wId}/slot/{sId}/download")]
[CheckForWorld]
[CheckRealmOwner]
public ActionResult<BackupDownloadResponse> GetBackup(int wId, int sId)
{
Backup backup = _context.Backups.Include(b => b.Slot).FirstOrDefault(b => b.Slot.World.Id == wId && b.Slot.Id == sId);
if (backup == null)
{
ErrorResponse errorResponse = new()
{
ErrorCode = 404,
ErrorMsg = "No backup found"
};
return NotFound(errorResponse);
}
BackupDownloadResponse backupDownloadResponse = new()
{
DownloadLink = backup.DownloadUrl,
ResourcePackUrl = backup.ResourcePackUrl,
ResourcePackHash = backup.ResourcePackHash,
};
return Ok(backupDownloadResponse);
}
[HttpGet("v1/{wId}/join/pc")]
public ActionResult<Connection> Join(int wId)
{

View File

@ -125,6 +125,7 @@ app.UseMiddleware<CheckRealmOwnerMiddleware>();
app.UseMiddleware<ActiveSubscriptionMiddleware>();
app.UseMiddleware<AdminKeyMiddleware>();
app.UseMiddleware<CheckForWorldMiddleware>();
app.UseMiddleware<RouteLoggingMiddleware>();
Console.WriteLine($"Running in {mode.Value} mode");
app.Run();

View File

@ -0,0 +1,9 @@
namespace Minecraft_Realms_Emulator.Responses
{
public class BackupDownloadResponse
{
public string DownloadLink { get; set; } = null!;
public string? ResourcePackUrl { get; set; }
public string? ResourcePackHash { get; set; }
}
}