Looks like we finally have a solution.

Change logging to just log a message, instead of the exception.
Moved logic to Syncer involvement.  When posting the data, if a 500 is
returned, then it is up to the Syncer to re-submit it.
This commit is contained in:
Mario Steele 2025-08-09 04:09:17 -05:00
parent 73955353cb
commit 004c490dd8
23 changed files with 529 additions and 177 deletions

View file

@ -66,6 +66,17 @@ public class DataContext : DbContext
.FirstOrDefaultAsync(ct); .FirstOrDefaultAsync(ct);
} }
public async Task CleanupChangeLogAsync(CancellationToken ct = default)
{
var logs = await ChangeLogs.ToListAsync(ct);
if (logs.Count < 50) return;
for (var i = 0; i < (logs.Count - 50); i++)
{
ChangeLogs.Remove(logs[i]);
}
await base.SaveChangesAsync(ct);
}
public DbSet<History> Histories { get; set; } public DbSet<History> Histories { get; set; }
public DbSet<Playlist> Playlists { get; set; } public DbSet<Playlist> Playlists { get; set; }
public DbSet<Profile> Profiles { get; set; } public DbSet<Profile> Profiles { get; set; }

View file

@ -1,40 +0,0 @@
using System.Collections;
namespace FreeTubeSync.Database;
public static class MapData
{
public static void MapFrom(this object obj2, object obj1)
{
var props = new Dictionary<string, object?>();
var t1Type = obj1.GetType();
var t2Type = obj2.GetType();
var properties = t1Type.GetProperties();
foreach (var property in properties)
{
props[property.Name] = property.GetValue(obj1);
}
properties = t2Type.GetProperties();
foreach (var property in properties)
{
if (props.ContainsKey(property.Name))
{
var value = props[property.Name];
if (value is string || value is not IEnumerable)
property.SetValue(obj2, value);
}
}
}
public static void MapTo<T1, T2>(this IEnumerable<T1> obj1, IList<T2> obj2)
where T1 : class where T2 : class, new()
{
foreach (var obj in obj1)
{
var newObj = new T2();
newObj.MapFrom(obj);
obj2.Add(newObj);
}
}
}

View file

@ -32,25 +32,22 @@ public static class HistoryEndpoint
{ {
try try
{ {
logger.LogInformation("History {id} does not exist, adding it to the database Thread: {thread}",
history._id, Thread.CurrentThread.ManagedThreadId);
await repository.AddAsync(history, ct); await repository.AddAsync(history, ct);
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.LogError(ex, "Failed to create history {json}", history._id); logger.LogError("Failed to create history {json}", history._id);
return Results.StatusCode(500); return Results.StatusCode(500);
} }
} }
else else
{ {
try logger.LogInformation("History {id} exists, updating the database. Thread: {thread}", history._id,
{ Thread.CurrentThread.ManagedThreadId);
await repository.UpdateAsync(history, ct); results.UpdateFrom(history);
} await repository.UpdateAsync(results, ct);
catch (Exception ex)
{
logger.LogError(ex, "Failed to update history {json}", history._id);
return Results.StatusCode(500);
}
} }
return Results.Ok(); return Results.Ok();
} }

View file

@ -7,10 +7,10 @@ public static class PingEndpoint
public static void MapPingEndpoints(this WebApplication app) public static void MapPingEndpoints(this WebApplication app)
{ {
var group = app.MapGroup("ping"); var group = app.MapGroup("ping");
group.MapGet("/", async (CancellationToken token) => group.MapGet("/", async (DataContext dbContext, CancellationToken token) =>
{ {
await Task.Delay(10); await dbContext.CleanupChangeLogAsync(token);
var dict = new { AppVersion = "0.1.4" }; var dict = new { AppVersion = "0.1.5" };
return Results.Ok(dict); return Results.Ok(dict);
}); });
@ -22,6 +22,7 @@ public static class PingEndpoint
return Results.NotFound(); return Results.NotFound();
var dict = new { LastUpdated = log.ChangeTime }; var dict = new { LastUpdated = log.ChangeTime };
await dbContext.CleanupChangeLogAsync(token);
return Results.Ok(dict); return Results.Ok(dict);
}); });
} }

View file

@ -34,24 +34,21 @@ public static class PlaylistEndpoint
{ {
try try
{ {
logger.LogInformation("Playlist {id} does not exist, adding it to the database", playlist._id);
await repository.AddAsync(playlist, ct); await repository.AddAsync(playlist, ct);
} }
catch (Exception ex) catch (Exception)
{ {
logger.LogError(ex, "Failed to create Playlist {playlist}", playlist._id); logger.LogError("Failed to create Playlist {playlist}", playlist._id);
return Results.StatusCode(500); return Results.StatusCode(500);
} }
} }
else else
{ {
// Add Update Code here logger.LogInformation("Playlist {id} exists, updating the databsae.", playlist._id);
results.UpdateFrom(playlist);
var toRemove = results.videos.Where(vid => !playlist.videos.Any(x => x.playlistItemId == vid.playlistItemId)).ToList(); var toRemove = results.videos.Where(vid => !playlist.videos.Any(x => x.playlistItemId == vid.playlistItemId)).ToList();
results.lastUpdatedAt = playlist.lastUpdatedAt;
results.@protected = playlist.@protected;
results.playlistName = playlist.playlistName;
results.createdAt = playlist.createdAt;
foreach (var vid in toRemove) foreach (var vid in toRemove)
results.videos.Remove(vid); results.videos.Remove(vid);

View file

@ -33,22 +33,21 @@ public static class ProfileEndpoint
{ {
try try
{ {
logger.LogInformation("Profile {id} does not exist, adding it to the database", profile._id);
await repository.AddAsync(profile, ct); await repository.AddAsync(profile, ct);
} }
catch (Exception ex) catch (Exception)
{ {
logger.LogError(ex, "Failed to create profile {json}", profile._id); logger.LogError("Failed to create profile {json}", profile._id);
return Results.StatusCode(500); return Results.StatusCode(500);
} }
} }
else else
{ {
logger.LogInformation("Profile {id} exists, updating the database.", profile._id);
res.UpdateFrom(profile);
var toRemove = res.subscriptions.Where(sub => !profile.subscriptions.Any(x => x.id == sub.id)).ToList(); var toRemove = res.subscriptions.Where(sub => !profile.subscriptions.Any(x => x.id == sub.id)).ToList();
res.name = profile.name;
res.textColor = profile.textColor;
res.bgColor = profile.bgColor;
foreach (var sub in toRemove) foreach (var sub in toRemove)
res.subscriptions.Remove(sub); res.subscriptions.Remove(sub);

View file

@ -34,25 +34,20 @@ public static class SearchHistoryEndpoint
{ {
try try
{ {
logger.LogInformation("SearchHistory {id} does not exist, adding it to the database", history._id);
await repository.AddAsync(history, ct); await repository.AddAsync(history, ct);
} }
catch (Exception e) catch (Exception)
{ {
logger.LogError(e, "Failed to update history {json}", history._id); logger.LogError("Failed to update history {json}", history._id);
return Results.StatusCode(500); return Results.StatusCode(500);
} }
} }
else else
{ {
try logger.LogInformation("SearchHistory {id} exists, updating the database.", history._id);
{ result.UpdateFrom(history);
await repository.UpdateAsync(history, ct); await repository.UpdateAsync(result, ct);
}
catch (Exception e)
{
logger.LogError(e, "Failed to update history {json}", history._id);
return Results.StatusCode(500);
}
} }
return Results.Ok(); return Results.Ok();

View file

@ -33,25 +33,20 @@ public static class SettingEndpoint
{ {
try try
{ {
logger.LogInformation("Setting {id} does not exist, adding it to the database", setting._id);
await repository.AddAsync(setting, ct); await repository.AddAsync(setting, ct);
} }
catch (Exception ex) catch (Exception)
{ {
logger.LogError(ex, "Failed to add setting {setting}", setting._id); logger.LogError("Failed to add setting {setting}", setting._id);
return Results.StatusCode(500); return Results.StatusCode(500);
} }
} }
else else
{ {
try logger.LogInformation("Setting {id} exists, updating the database.", setting._id);
{ res.UpdateFrom(setting);
await repository.UpdateAsync(setting, ct); await repository.UpdateAsync(res, ct);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to update setting {setting}", setting._id);
return Results.StatusCode(500);
}
} }
return Results.Ok(); return Results.Ok();
} }

View file

@ -0,0 +1,257 @@
// <auto-generated />
using System;
using FreeTubeSync.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FreeTubeSync.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250808221748_UpdateNewLayout")]
partial class UpdateNewLayout
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.Entity("FreeTubeSync.Model.ChangeLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("ChangeTime")
.HasColumnType("TEXT");
b.Property<string>("ChangeType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TableName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ChangeLogs");
});
modelBuilder.Entity("FreeTubeSync.Model.History", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("isLive")
.HasColumnType("INTEGER");
b.Property<string>("lastViewedPlaylistItemId")
.HasColumnType("TEXT");
b.Property<string>("lastViewedPlaylistType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("lengthSeconds")
.HasColumnType("INTEGER");
b.Property<long>("published")
.HasColumnType("INTEGER");
b.Property<long>("timeWatched")
.HasColumnType("INTEGER");
b.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("videoId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("viewCount")
.HasColumnType("INTEGER");
b.Property<float>("watchProgress")
.HasColumnType("REAL");
b.HasKey("_id");
b.ToTable("Histories");
});
modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<long>("createdAt")
.HasColumnType("INTEGER");
b.Property<long>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.Property<string>("playlistName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("protected")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("Playlists");
});
modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("bgColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("textColor")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("_id");
b.ToTable("Profiles");
});
modelBuilder.Entity("FreeTubeSync.Model.SearchHistory", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<long>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("SearchHistories");
});
modelBuilder.Entity("FreeTubeSync.Model.Setting", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("_id");
b.ToTable("Settings");
});
modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{
b.OwnsMany("FreeTubeSync.Model.Video", "videos", b1 =>
{
b1.Property<string>("Playlist_id")
.HasColumnType("TEXT");
b1.Property<string>("videoId")
.HasColumnType("TEXT");
b1.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<string>("lengthSeconds")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<string>("playlistItemId")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<long>("published")
.HasColumnType("INTEGER");
b1.Property<long>("timeAdded")
.HasColumnType("INTEGER");
b1.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b1.HasKey("Playlist_id", "videoId");
b1.ToTable("Video");
b1.WithOwner()
.HasForeignKey("Playlist_id");
});
b.Navigation("videos");
});
modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{
b.OwnsMany("FreeTubeSync.Model.Subscription", "subscriptions", b1 =>
{
b1.Property<string>("Profile_id")
.HasColumnType("TEXT");
b1.Property<string>("id")
.HasColumnType("TEXT");
b1.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<string>("thumbnail")
.HasColumnType("TEXT");
b1.HasKey("Profile_id", "id");
b1.ToTable("Subscription");
b1.WithOwner()
.HasForeignKey("Profile_id");
});
b.Navigation("subscriptions");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,130 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FreeTubeSync.Migrations
{
/// <inheritdoc />
public partial class UpdateNewLayout : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Subscriptions");
migrationBuilder.DropTable(
name: "Videos");
migrationBuilder.CreateTable(
name: "Subscription",
columns: table => new
{
id = table.Column<string>(type: "TEXT", nullable: false),
Profile_id = table.Column<string>(type: "TEXT", nullable: false),
name = table.Column<string>(type: "TEXT", nullable: false),
thumbnail = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscription", x => new { x.Profile_id, x.id });
table.ForeignKey(
name: "FK_Subscription_Profiles_Profile_id",
column: x => x.Profile_id,
principalTable: "Profiles",
principalColumn: "_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Video",
columns: table => new
{
videoId = table.Column<string>(type: "TEXT", nullable: false),
Playlist_id = table.Column<string>(type: "TEXT", nullable: false),
title = table.Column<string>(type: "TEXT", nullable: false),
author = table.Column<string>(type: "TEXT", nullable: false),
authorId = table.Column<string>(type: "TEXT", nullable: false),
lengthSeconds = table.Column<string>(type: "TEXT", nullable: false),
published = table.Column<long>(type: "INTEGER", nullable: false),
timeAdded = table.Column<long>(type: "INTEGER", nullable: false),
playlistItemId = table.Column<string>(type: "TEXT", nullable: false),
type = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Video", x => new { x.Playlist_id, x.videoId });
table.ForeignKey(
name: "FK_Video_Playlists_Playlist_id",
column: x => x.Playlist_id,
principalTable: "Playlists",
principalColumn: "_id",
onDelete: ReferentialAction.Cascade);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Subscription");
migrationBuilder.DropTable(
name: "Video");
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
id = table.Column<string>(type: "TEXT", nullable: false),
Profile_id = table.Column<string>(type: "TEXT", nullable: true),
name = table.Column<string>(type: "TEXT", nullable: false),
thumbnail = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.id);
table.ForeignKey(
name: "FK_Subscriptions_Profiles_Profile_id",
column: x => x.Profile_id,
principalTable: "Profiles",
principalColumn: "_id");
});
migrationBuilder.CreateTable(
name: "Videos",
columns: table => new
{
playlistItemId = table.Column<string>(type: "TEXT", nullable: false),
Playlist_id = table.Column<string>(type: "TEXT", nullable: true),
author = table.Column<string>(type: "TEXT", nullable: false),
authorId = table.Column<string>(type: "TEXT", nullable: false),
lengthSeconds = table.Column<string>(type: "TEXT", nullable: false),
pubished = table.Column<long>(type: "INTEGER", nullable: false),
timeAdded = table.Column<long>(type: "INTEGER", nullable: false),
title = table.Column<string>(type: "TEXT", nullable: false),
type = table.Column<string>(type: "TEXT", nullable: false),
videoId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Videos", x => x.playlistItemId);
table.ForeignKey(
name: "FK_Videos_Playlists_Playlist_id",
column: x => x.Playlist_id,
principalTable: "Playlists",
principalColumn: "_id");
});
migrationBuilder.CreateIndex(
name: "IX_Subscriptions_Profile_id",
table: "Subscriptions",
column: "Profile_id");
migrationBuilder.CreateIndex(
name: "IX_Videos_Playlist_id",
table: "Videos",
column: "Playlist_id");
}
}
}

View file

@ -17,7 +17,7 @@ namespace FreeTubeSync.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7"); modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.Entity("FreeTubeSync.Model.Database.ChangeLog", b => modelBuilder.Entity("FreeTubeSync.Model.ChangeLog", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -39,7 +39,7 @@ namespace FreeTubeSync.Migrations
b.ToTable("ChangeLogs"); b.ToTable("ChangeLogs");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.History", b => modelBuilder.Entity("FreeTubeSync.Model.History", b =>
{ {
b.Property<string>("_id") b.Property<string>("_id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -98,7 +98,7 @@ namespace FreeTubeSync.Migrations
b.ToTable("Histories"); b.ToTable("Histories");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.Playlist", b => modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{ {
b.Property<string>("_id") b.Property<string>("_id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -121,7 +121,7 @@ namespace FreeTubeSync.Migrations
b.ToTable("Playlists"); b.ToTable("Playlists");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.Profile", b => modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{ {
b.Property<string>("_id") b.Property<string>("_id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -143,7 +143,7 @@ namespace FreeTubeSync.Migrations
b.ToTable("Profiles"); b.ToTable("Profiles");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.SearchHistory", b => modelBuilder.Entity("FreeTubeSync.Model.SearchHistory", b =>
{ {
b.Property<string>("_id") b.Property<string>("_id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -156,7 +156,7 @@ namespace FreeTubeSync.Migrations
b.ToTable("SearchHistories"); b.ToTable("SearchHistories");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.Setting", b => modelBuilder.Entity("FreeTubeSync.Model.Setting", b =>
{ {
b.Property<string>("_id") b.Property<string>("_id")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -170,94 +170,82 @@ namespace FreeTubeSync.Migrations
b.ToTable("Settings"); b.ToTable("Settings");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.Subscription", b => modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{ {
b.Property<string>("id") b.OwnsMany("FreeTubeSync.Model.Video", "videos", b1 =>
.HasColumnType("TEXT"); {
b1.Property<string>("Playlist_id")
.HasColumnType("TEXT");
b.Property<string>("Profile_id") b1.Property<string>("videoId")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("name") b1.Property<string>("author")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("thumbnail") b1.Property<string>("authorId")
.HasColumnType("TEXT"); .IsRequired()
.HasColumnType("TEXT");
b.HasKey("id"); b1.Property<string>("lengthSeconds")
.IsRequired()
.HasColumnType("TEXT");
b.HasIndex("Profile_id"); b1.Property<string>("playlistItemId")
.IsRequired()
.HasColumnType("TEXT");
b.ToTable("Subscriptions"); b1.Property<long>("published")
}); .HasColumnType("INTEGER");
modelBuilder.Entity("FreeTubeSync.Model.Database.Video", b => b1.Property<long>("timeAdded")
{ .HasColumnType("INTEGER");
b.Property<string>("playlistItemId")
.HasColumnType("TEXT");
b.Property<string>("Playlist_id") b1.Property<string>("title")
.HasColumnType("TEXT"); .IsRequired()
.HasColumnType("TEXT");
b.Property<string>("author") b1.Property<string>("type")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("authorId") b1.HasKey("Playlist_id", "videoId");
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("lengthSeconds") b1.ToTable("Video");
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("pubished") b1.WithOwner()
.HasColumnType("INTEGER"); .HasForeignKey("Playlist_id");
});
b.Property<long>("timeAdded")
.HasColumnType("INTEGER");
b.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("videoId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("playlistItemId");
b.HasIndex("Playlist_id");
b.ToTable("Videos");
});
modelBuilder.Entity("FreeTubeSync.Model.Database.Subscription", b =>
{
b.HasOne("FreeTubeSync.Model.Database.Profile", null)
.WithMany("subscriptions")
.HasForeignKey("Profile_id");
});
modelBuilder.Entity("FreeTubeSync.Model.Database.Video", b =>
{
b.HasOne("FreeTubeSync.Model.Database.Playlist", null)
.WithMany("videos")
.HasForeignKey("Playlist_id");
});
modelBuilder.Entity("FreeTubeSync.Model.Database.Playlist", b =>
{
b.Navigation("videos"); b.Navigation("videos");
}); });
modelBuilder.Entity("FreeTubeSync.Model.Database.Profile", b => modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{ {
b.OwnsMany("FreeTubeSync.Model.Subscription", "subscriptions", b1 =>
{
b1.Property<string>("Profile_id")
.HasColumnType("TEXT");
b1.Property<string>("id")
.HasColumnType("TEXT");
b1.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b1.Property<string>("thumbnail")
.HasColumnType("TEXT");
b1.HasKey("Profile_id", "id");
b1.ToTable("Subscription");
b1.WithOwner()
.HasForeignKey("Profile_id");
});
b.Navigation("subscriptions"); b.Navigation("subscriptions");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618

View file

@ -0,0 +1,17 @@
using System.Collections;
namespace FreeTubeSync.Model;
public class BaseModel
{
public void UpdateFrom(object other)
{
foreach (var prop in other.GetType().GetProperties())
{
if (prop.Name == "_id" || prop.Name == "id" || prop.Name == "Id" || prop.Name == "subscriptions" || prop.Name == "videos")
continue;
var val = prop.GetValue(other);
prop.SetValue(this, val);
}
}
}

View file

@ -1,6 +1,6 @@
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
public class ChangeLog public class ChangeLog : BaseModel
{ {
public int Id { get; set; } public int Id { get; set; }
public string TableName { get; set; } = string.Empty; public string TableName { get; set; } = string.Empty;

View file

@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
public class History public class History : BaseModel
{ {
[Key] [Key]
public string _id { get; set; } = string.Empty; public string _id { get; set; } = string.Empty;

View file

@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
public class Playlist public class Playlist : BaseModel
{ {
[Key] [Key]
public string _id { get; set; } = string.Empty; public string _id { get; set; } = string.Empty;

View file

@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
public class Profile public class Profile : BaseModel
{ {
[Key] [Key]
public string _id { get; set; } = string.Empty; public string _id { get; set; } = string.Empty;

View file

@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
public class SearchHistory public class SearchHistory : BaseModel
{ {
[Key] [Key]
public string _id { get; set; } = string.Empty; public string _id { get; set; } = string.Empty;

View file

@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
public class Setting public class Setting : BaseModel
{ {
[Key] [Key]
public string _id { get; set; } public string _id { get; set; }

View file

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
[Owned] [Owned]
public class Subscription public class Subscription : BaseModel
{ {
public string id { get; set; } = string.Empty; public string id { get; set; } = string.Empty;
public string name { get; set; } = string.Empty; public string name { get; set; } = string.Empty;

View file

@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
namespace FreeTubeSync.Model; namespace FreeTubeSync.Model;
[Owned] [Owned]
public class Video public class Video : BaseModel
{ {
public string videoId { get; set; } = string.Empty; public string videoId { get; set; } = string.Empty;
public string title { get; set; } = string.Empty; public string title { get; set; } = string.Empty;

View file

@ -23,8 +23,11 @@ public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
public async Task UpdateAsync(TEntity entity, CancellationToken ct, bool sync = true) public async Task UpdateAsync(TEntity entity, CancellationToken ct, bool sync = true)
{ {
_dbSet.Attach(entity); if (!_dbSet.Local.Contains(entity))
_context.Entry(entity).State = EntityState.Modified; {
_dbSet.Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
}
if (sync) if (sync)
await _context.SaveChangesAsync(ct); await _context.SaveChangesAsync(ct);
} }

View file

@ -5,7 +5,8 @@
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
} }
} }
} }

View file

@ -5,7 +5,8 @@
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*"