Compare commits

...

6 commits

Author SHA1 Message Date
6f920f3f0d Updated Program
Added Mapping of Ping Endpoints.
2025-07-31 13:02:24 -05:00
5387e1beb7 Updated Data Context
Added code to track changes that are made to the database.
2025-07-31 13:01:37 -05:00
57b6f24e35 Updated Migrations
Added Migration to add Change Log Tracking.
2025-07-31 13:00:51 -05:00
72dca6c1fe Updated DataContext
Added DbSet for ChangeLog table.
2025-07-31 13:00:32 -05:00
e2d0c327eb Updated ProfileEndpoint
Added an UpdateAsync() call for each subscription, to ensure that
subscriptions actually get updated in the database.
2025-07-31 12:59:39 -05:00
c3cd7b5a16 Added ChangeLog
Added change log to track updates to the database, for easier pinging
from Syncer client.
2025-07-31 12:57:47 -05:00
8 changed files with 420 additions and 1 deletions

View file

@ -18,6 +18,53 @@ public class DataContext : DbContext
modelBuilder.Entity<Playlist>()
.Navigation(e => e.videos).AutoInclude();
}
public override int SaveChanges()
{
TrackChanges();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
TrackChanges();
return await base.SaveChangesAsync(ct);
}
private void TrackChanges()
{
var changedEntries = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.ToList();
foreach (var entry in changedEntries)
{
var log = new ChangeLog
{
TableName = entry.Metadata.GetTableName() ?? entry.Entity.GetType().Name,
ChangeType = entry.State.ToString(),
ChangeTime = DateTime.UtcNow
};
ChangeLogs.Add(log);
}
}
public ChangeLog? GetLatestChange()
{
return ChangeLogs
.OrderByDescending(cl => cl.ChangeTime)
.FirstOrDefault();
}
public async Task<ChangeLog?> GetLatestChangeAsync(CancellationToken ct = default)
{
return await ChangeLogs
.OrderByDescending(cl => cl.ChangeTime)
.FirstOrDefaultAsync(ct);
}
public DbSet<History> Histories { get; set; }
public DbSet<Playlist> Playlists { get; set; }
@ -26,4 +73,5 @@ public class DataContext : DbContext
public DbSet<Setting> Settings { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
public DbSet<Video> Videos { get; set; }
public DbSet<ChangeLog> ChangeLogs { get; set; }
}

View file

@ -0,0 +1,29 @@
using FreeTubeSync.Database;
using FreeTubeSync.Model.Database;
namespace FreeTubeSync.EndPoints;
public static class PingEndpoint
{
public static void MapPingEndpoints(this WebApplication app)
{
var group = app.MapGroup("ping");
group.MapGet("/", async (CancellationToken token) =>
{
await Task.Delay(10);
var dict = new { AppVersion = "0.1.3" };
return Results.Ok(dict);
});
group.MapGet("/lastUpdated", async (DataContext dbContext, CancellationToken token) =>
{
var log = await dbContext.GetLatestChangeAsync(token);
if (log == null)
return Results.NotFound();
var dict = new { LastUpdated = log.ChangeTime };
return Results.Ok(dict);
});
}
}

View file

@ -51,7 +51,10 @@ public static class ProfileEndpoint
if (f == null)
notFound.Add(subscription);
else
{
subscription.MapFrom(f);
await subRepo.UpdateAsync(subscription, ct, false);
}
}
var newSubs = (from subscription in profileJson.subscriptions let f = res.subscriptions.FirstOrDefault(s => s.id == subscription.id) where f == null select subscription).ToList();
@ -72,7 +75,7 @@ public static class ProfileEndpoint
{
res.subscriptions.Remove(nfSub);
}
await repository.UpdateAsync(res, ct);
}

View file

@ -0,0 +1,269 @@
// <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("20250731174538_AddChangeLogTracking")]
partial class AddChangeLogTracking
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.Entity("FreeTubeSync.Model.Database.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.Database.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.Database.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.Database.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.Database.SearchHistory", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<long>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("SearchHistories");
});
modelBuilder.Entity("FreeTubeSync.Model.Database.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.Database.Subscription", b =>
{
b.Property<string>("id")
.HasColumnType("TEXT");
b.Property<string>("Profile_id")
.HasColumnType("TEXT");
b.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("thumbnail")
.HasColumnType("TEXT");
b.HasKey("id");
b.HasIndex("Profile_id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("FreeTubeSync.Model.Database.Video", b =>
{
b.Property<string>("playlistItemId")
.HasColumnType("TEXT");
b.Property<string>("Playlist_id")
.HasColumnType("TEXT");
b.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("lengthSeconds")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("pubished")
.HasColumnType("INTEGER");
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");
});
modelBuilder.Entity("FreeTubeSync.Model.Database.Profile", b =>
{
b.Navigation("subscriptions");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FreeTubeSync.Migrations
{
/// <inheritdoc />
public partial class AddChangeLogTracking : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ChangeLogs",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
TableName = table.Column<string>(type: "TEXT", nullable: false),
ChangeType = table.Column<string>(type: "TEXT", nullable: false),
ChangeTime = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChangeLogs", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ChangeLogs");
}
}
}

View file

@ -1,4 +1,5 @@
// <auto-generated />
using System;
using FreeTubeSync.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -16,6 +17,28 @@ namespace FreeTubeSync.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.Entity("FreeTubeSync.Model.Database.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.Database.History", b =>
{
b.Property<string>("_id")

View file

@ -0,0 +1,9 @@
namespace FreeTubeSync.Model.Database;
public class ChangeLog
{
public int Id { get; set; }
public string TableName { get; set; } = string.Empty;
public string ChangeType { get; set; } = string.Empty;
public DateTime ChangeTime { get; set; } = DateTime.MinValue;
}

View file

@ -22,6 +22,7 @@ app.MapPlaylistEndpoints();
app.MapProfileEndpoints();
app.MapSearchHistoryEndpoints();
app.MapSettingEndpoints();
app.MapPingEndpoints();
await using(var serviceScope = app.Services.CreateAsyncScope())
await using (var dbContext = serviceScope.ServiceProvider.GetRequiredService<DataContext>())