using Microsoft.Data.Sqlite; using ytLive.Models; namespace ytLive.Services; public partial class LayoutStore : IDisposable { // ─── YPP journey-tracker snapshots (TASK 39) ─── /// Appends one YPP stats snapshot (append-only history; velocity/ETA /// read it in a later analytics slice). public void SaveYppSnapshot(YppStatSnapshot snapshot) { using var cmd = _connection.CreateCommand(); cmd.CommandText = """ INSERT INTO YppSnapshot (CapturedAtUtc, SubscriberCount, VideoCount, TotalViews, UploadsLast90Days, CommunityGuidelinesGoodStanding, CopyrightStrikesGoodStanding, ContentIdClaimsGoodStanding, OverallGoodStanding) VALUES ($captured, $subs, $videos, $views, $uploads, $cg, $cs, $cic, $overall); """; cmd.Parameters.AddWithValue("$captured", snapshot.CapturedAtUtc.ToString("o")); cmd.Parameters.AddWithValue("$subs", snapshot.SubscriberCount); cmd.Parameters.AddWithValue("$videos", snapshot.VideoCount); cmd.Parameters.AddWithValue("$views", snapshot.TotalViews); cmd.Parameters.AddWithValue("$uploads", snapshot.UploadsLast90Days); cmd.Parameters.AddWithValue("$cg", snapshot.CommunityGuidelinesGoodStanding ? 1 : 0); cmd.Parameters.AddWithValue("$cs", snapshot.CopyrightStrikesGoodStanding ? 1 : 0); cmd.Parameters.AddWithValue("$cic", snapshot.ContentIdClaimsGoodStanding ? 1 : 0); cmd.Parameters.AddWithValue("$overall", snapshot.OverallGoodStanding ? 1 : 0); cmd.ExecuteNonQuery(); } /// Most recent snapshots, newest first (defaults to the full history). public List LoadYppSnapshots(int limit = 365) { var result = new List(); using var cmd = _connection.CreateCommand(); cmd.CommandText = """ SELECT CapturedAtUtc, SubscriberCount, VideoCount, TotalViews, UploadsLast90Days, CommunityGuidelinesGoodStanding, CopyrightStrikesGoodStanding, ContentIdClaimsGoodStanding, OverallGoodStanding FROM YppSnapshot ORDER BY CapturedAtUtc DESC LIMIT $limit; """; cmd.Parameters.AddWithValue("$limit", limit); using var reader = cmd.ExecuteReader(); while (reader.Read()) { result.Add(new YppStatSnapshot { CapturedAtUtc = DateTime.TryParse(reader.GetString(0), out var captured) ? captured : DateTime.MinValue, SubscriberCount = reader.GetInt64(1), VideoCount = reader.GetInt64(2), TotalViews = reader.GetInt64(3), UploadsLast90Days = reader.GetInt32(4), CommunityGuidelinesGoodStanding = reader.GetInt32(5) != 0, CopyrightStrikesGoodStanding = reader.GetInt32(6) != 0, ContentIdClaimsGoodStanding = reader.GetInt32(7) != 0, OverallGoodStanding = reader.GetInt32(8) != 0, }); } return result; } /// The manually self-reported YPP checklist state (2FA / AdSense are /// not readable from any YouTube API — the creator ticks them in-app). public (bool TwoFactorEnabled, bool AdSenseLinked) LoadYppChecklist() { return (GetSetting("Ypp.TwoFactorEnabled") == "1", GetSetting("Ypp.AdSenseLinked") == "1"); } public void SaveYppChecklist(bool twoFactorEnabled, bool adsenseLinked) { UpsertSetting("Ypp.TwoFactorEnabled", twoFactorEnabled ? "1" : "0"); UpsertSetting("Ypp.AdSenseLinked", adsenseLinked ? "1" : "0"); } /// UTC timestamp of the last successful stats refresh (used to gate /// the once-daily auto-refresh against the analytics quota-thin API). public DateTime? LoadYppLastRefreshedAtUtc() { var value = GetSetting("Ypp.LastRefreshedAtUtc"); return DateTime.TryParse(value, out var dt) ? dt : null; } public void SaveYppLastRefreshedAtUtc(DateTime utc) { UpsertSetting("Ypp.LastRefreshedAtUtc", utc.ToString("o")); } }