- Notifications
+
+ Notifications
+
+ These settings only apply to the {sport === 'CFB' ? 'college football' : 'NFL'} app — each IV League app has its own.
+
+
diff --git a/Client.React/src/services/push.ts b/Client.React/src/services/push.ts
index c86a49cc..627d88cd 100644
--- a/Client.React/src/services/push.ts
+++ b/Client.React/src/services/push.ts
@@ -1,7 +1,7 @@
import type { PushSubscriptionRequestDto } from '../types/notifications';
import { isStandalonePwa } from '../utils/pwa';
import { decodeBase64Url } from '../utils/base64';
-import type { SportType } from './sport';
+import { toLeagueType, type SportType } from './sport';
export function isPushSupported(): boolean {
return (
@@ -63,7 +63,7 @@ export async function subscribeBrowserToPush(
export function toSubscriptionRequest(subscription: PushSubscription, sport: SportType): PushSubscriptionRequestDto {
const json = subscription.toJSON();
return {
- sport: sport === 'CFB' ? 1 : 0,
+ sport: toLeagueType(sport),
endpoint: subscription.endpoint,
p256dh: json.keys?.p256dh ?? '',
auth: json.keys?.auth ?? '',
diff --git a/Client.React/src/services/sport.tsx b/Client.React/src/services/sport.tsx
index e83a5880..7335adcf 100644
--- a/Client.React/src/services/sport.tsx
+++ b/Client.React/src/services/sport.tsx
@@ -2,6 +2,9 @@ import { createContext, useContext, useMemo } from 'react';
export type SportType = 'NFL' | 'CFB';
+/** The server's LeagueType enum value for a sport: 0 = NFL, 1 = CFB. */
+export const toLeagueType = (sport: SportType): 0 | 1 => (sport === 'CFB' ? 1 : 0);
+
interface SportsContextValue {
sport: SportType;
isCfb: boolean;
diff --git a/Server.UnitTests/NotificationDispatcherTests.cs b/Server.UnitTests/NotificationDispatcherTests.cs
index e4af7f2f..a3c8186b 100644
--- a/Server.UnitTests/NotificationDispatcherTests.cs
+++ b/Server.UnitTests/NotificationDispatcherTests.cs
@@ -40,7 +40,7 @@ private NotificationDispatcher BuildDispatcher(bool isDevelopment, string? railw
[Fact]
public async Task DispatchAsync_WhenToggleDisabled_NeverLoadsSubscriptions_NeverSends()
{
- _preferencesService.GetAsync("user-1").Returns(new NotificationPreferencesDto { NotifyWeekResult = false });
+ _preferencesService.GetAsync("user-1", Arg.Any()).Returns(new NotificationPreferencesDto { NotifyWeekResult = false });
await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production")
.DispatchAsync("user-1", p => p.NotifyWeekResult, new PushPayload("T", "B"));
@@ -52,7 +52,7 @@ await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production"
[Fact]
public async Task DispatchAsync_WhenToggleEnabled_ButNoSubscribedDevices_DoesNotSend()
{
- _preferencesService.GetAsync("user-1").Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
+ _preferencesService.GetAsync("user-1", Arg.Any()).Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
_subscriptionService.GetForUserAsync("user-1").Returns([]);
await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production")
@@ -64,7 +64,7 @@ await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production"
[Fact]
public async Task DispatchAsync_InProduction_SendsToEveryDevice()
{
- _preferencesService.GetAsync("user-1").Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
+ _preferencesService.GetAsync("user-1", Arg.Any()).Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
var subscriptions = OneSubscription();
_subscriptionService.GetForUserAsync("user-1").Returns(subscriptions);
@@ -80,7 +80,7 @@ await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production"
[InlineData(true, null)] // local dev run: IsDevelopment()=true, no Railway var at all
public async Task DispatchAsync_OutsideProduction_SuppressesSend_ButStillConsumesTheToggleCheck(bool isDevelopment, string? railwayEnvironmentName)
{
- _preferencesService.GetAsync("user-1").Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
+ _preferencesService.GetAsync("user-1", Arg.Any()).Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
_subscriptionService.GetForUserAsync("user-1").Returns(OneSubscription());
await BuildDispatcher(isDevelopment, railwayEnvironmentName)
@@ -90,8 +90,7 @@ await BuildDispatcher(isDevelopment, railwayEnvironmentName)
}
// Each installed app (NFL at ivleague.xyz, CFB at cfb.ivleague.xyz) registers its own
- // subscription — an NFL alert must only reach the NFL app. A subscription saved before sport
- // was tracked (null) keeps getting every sport, so nobody who enabled push loses anything.
+ // subscription — an NFL alert must only ever reach the NFL app.
private static List NflCfbAndUnlabeled() => [
new() { Id = 1, UserId = "user-1", Endpoint = "https://push.example/nfl", P256dh = "p", Auth = "a", Sport = LeagueType.Nfl },
new() { Id = 2, UserId = "user-1", Endpoint = "https://push.example/cfb", P256dh = "p", Auth = "a", Sport = LeagueType.Cfb },
@@ -101,25 +100,25 @@ private static List NflCfbAndUnlabeled() => [
[Theory]
[InlineData(LeagueType.Nfl, "https://push.example/nfl", "https://push.example/cfb")]
[InlineData(LeagueType.Cfb, "https://push.example/cfb", "https://push.example/nfl")]
- public async Task DispatchAsync_SendsOnlyToThatSportsApp_PlusUnlabeledSubscriptions(LeagueType sport, string expected, string excluded)
+ public async Task DispatchAsync_SendsOnlyToThatSportsApp(LeagueType sport, string expected, string excluded)
{
- _preferencesService.GetAsync("user-1").Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
+ _preferencesService.GetAsync("user-1", Arg.Any()).Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
_subscriptionService.GetForUserAsync("user-1").Returns(NflCfbAndUnlabeled());
await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production")
.DispatchAsync("user-1", p => p.NotifyWeekResult, new PushPayload("T", "B", Sport: sport));
await _pushSender.Received(1).SendAsync(Arg.Is(s => s.Endpoint == expected), Arg.Any(), Arg.Any());
- await _pushSender.Received(1).SendAsync(Arg.Is(s => s.Endpoint == "https://push.example/old"), Arg.Any(), Arg.Any());
+ await _pushSender.DidNotReceive().SendAsync(Arg.Is(s => s.Endpoint == "https://push.example/old"), Arg.Any(), Arg.Any());
await _pushSender.DidNotReceive().SendAsync(Arg.Is(s => s.Endpoint == excluded), Arg.Any(), Arg.Any());
}
- // Many users install only one app but are in both sports' leagues — a push must never be
- // dropped just because the user has no app for that sport (a real NFL alert was, 2026-10-04).
+ // Owner's rule: notifications only go to the proper app for that sport — a user who only
+ // enabled push in the college app gets no NFL alerts there.
[Fact]
- public async Task DispatchAsync_UserWithOnlyTheOtherSportsApp_StillGetsThePush()
+ public async Task DispatchAsync_UserWithOnlyTheOtherSportsApp_DoesNotGetThePush()
{
- _preferencesService.GetAsync("user-1").Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
+ _preferencesService.GetAsync("user-1", Arg.Any()).Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
_subscriptionService.GetForUserAsync("user-1").Returns([
new PushSubscription { Id = 1, UserId = "user-1", Endpoint = "https://push.example/cfb", P256dh = "p", Auth = "a", Sport = LeagueType.Cfb },
]);
@@ -127,6 +126,21 @@ public async Task DispatchAsync_UserWithOnlyTheOtherSportsApp_StillGetsThePush()
await BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production")
.DispatchAsync("user-1", p => p.NotifyWeekResult, new PushPayload("T", "B", Sport: LeagueType.Nfl));
- await _pushSender.Received(1).SendAsync(Arg.Is(s => s.Endpoint == "https://push.example/cfb"), Arg.Any(), Arg.Any());
+ await _pushSender.DidNotReceive().SendAsync(Arg.Any(), Arg.Any(), Arg.Any());
+ }
+
+ [Fact]
+ public async Task DispatchAsync_UsesTheSettingsForTheAlertsSport()
+ {
+ _preferencesService.GetAsync("user-1", LeagueType.Nfl).Returns(new NotificationPreferencesDto { NotifyWeekResult = true });
+ _preferencesService.GetAsync("user-1", LeagueType.Cfb).Returns(new NotificationPreferencesDto { NotifyWeekResult = false });
+ _subscriptionService.GetForUserAsync("user-1").Returns(NflCfbAndUnlabeled());
+ var dispatcher = BuildDispatcher(isDevelopment: false, railwayEnvironmentName: "production");
+
+ await dispatcher.DispatchAsync("user-1", p => p.NotifyWeekResult, new PushPayload("T", "college", Sport: LeagueType.Cfb));
+ await dispatcher.DispatchAsync("user-1", p => p.NotifyWeekResult, new PushPayload("T", "nfl", Sport: LeagueType.Nfl));
+
+ await _pushSender.Received(1).SendAsync(Arg.Any(), Arg.Is(p => p.Body == "nfl"), Arg.Any());
+ await _pushSender.DidNotReceive().SendAsync(Arg.Any(), Arg.Is(p => p.Body == "college"), Arg.Any());
}
}
diff --git a/Server.UnitTests/NotificationPreferencesServiceTests.cs b/Server.UnitTests/NotificationPreferencesServiceTests.cs
index 179c57a1..ae4c0e3b 100644
--- a/Server.UnitTests/NotificationPreferencesServiceTests.cs
+++ b/Server.UnitTests/NotificationPreferencesServiceTests.cs
@@ -1,3 +1,4 @@
+using FourPlayWebApp.Shared.Models.Enum;
using FourPlayWebApp.Server.Services;
using FourPlayWebApp.Shared.Models.Data.Dtos;
using Microsoft.EntityFrameworkCore;
@@ -15,7 +16,7 @@ await SqliteTestDb.WithDb(nameof(GetAsync_WhenNoRowExists_ReturnsAllOffDefaults)
await using (var seed = SqliteTestDb.Open(nameof(GetAsync_WhenNoRowExists_ReturnsAllOffDefaults)))
await SqliteTestDb.SeedUser(seed, "alice");
- var result = await new NotificationPreferencesService(factory).GetAsync("alice");
+ var result = await new NotificationPreferencesService(factory).GetAsync("alice", LeagueType.Nfl);
Assert.False(result.NotifyMineBloodyDuringGame);
Assert.False(result.NotifyMineBloodyAtFinal);
@@ -38,7 +39,7 @@ await SqliteTestDb.WithDb(db, async factory =>
await using (var seed = SqliteTestDb.Open(db))
await SqliteTestDb.SeedUser(seed, "alice");
- var saved = await new NotificationPreferencesService(factory).UpsertAsync("alice", new NotificationPreferencesDto
+ var saved = await new NotificationPreferencesService(factory).UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto
{
NotifyMineCoveringAtFinal = true,
NotifyWeekResult = true,
@@ -48,7 +49,7 @@ await SqliteTestDb.WithDb(db, async factory =>
Assert.True(saved.NotifyWeekResult);
Assert.False(saved.NotifyMineBloodyDuringGame);
- var reread = await new NotificationPreferencesService(factory).GetAsync("alice");
+ var reread = await new NotificationPreferencesService(factory).GetAsync("alice", LeagueType.Nfl);
Assert.True(reread.NotifyMineCoveringAtFinal);
Assert.True(reread.NotifyWeekResult);
});
@@ -64,10 +65,10 @@ await SqliteTestDb.WithDb(db, async factory =>
await SqliteTestDb.SeedUser(seed, "alice");
var service = new NotificationPreferencesService(factory);
- await service.UpsertAsync("alice", new NotificationPreferencesDto { NotifyMineBloodyAtFinal = true });
- await service.UpsertAsync("alice", new NotificationPreferencesDto { NotifyMineBloodyAtFinal = false, NotifyWeekResult = true });
+ await service.UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto { NotifyMineBloodyAtFinal = true });
+ await service.UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto { NotifyMineBloodyAtFinal = false, NotifyWeekResult = true });
- var result = await service.GetAsync("alice");
+ var result = await service.GetAsync("alice", LeagueType.Nfl);
Assert.False(result.NotifyMineBloodyAtFinal);
Assert.True(result.NotifyWeekResult);
@@ -89,9 +90,9 @@ await SqliteTestDb.WithDb(db, async factory =>
}
var service = new NotificationPreferencesService(factory);
- await service.UpsertAsync("alice", new NotificationPreferencesDto { NotifyWeekResult = true });
+ await service.UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto { NotifyWeekResult = true });
- var bobsPrefs = await service.GetAsync("bob");
+ var bobsPrefs = await service.GetAsync("bob", LeagueType.Nfl);
Assert.False(bobsPrefs.NotifyWeekResult);
});
}
@@ -108,7 +109,7 @@ await SqliteTestDb.WithDb(db, async factory =>
await SqliteTestDb.SeedUser(seed, "alice");
var service = new NotificationPreferencesService(factory);
- await service.UpsertAsync("alice", new NotificationPreferencesDto { NotifyWeekResult = true });
+ await service.UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto { NotifyWeekResult = true });
Assert.False(await service.AnyLiveNotificationPreferenceEnabledAsync());
});
@@ -124,9 +125,31 @@ await SqliteTestDb.WithDb(db, async factory =>
await SqliteTestDb.SeedUser(seed, "alice");
var service = new NotificationPreferencesService(factory);
- await service.UpsertAsync("alice", new NotificationPreferencesDto { NotifyOthersCoveringAtFinal = true });
+ await service.UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto { NotifyOthersCoveringAtFinal = true });
Assert.True(await service.AnyLiveNotificationPreferenceEnabledAsync());
});
}
+
+ // Each app (NFL host / CFB host) has its own notification settings — "I may want NFL and not
+ // college" — saving one sport must never change the other.
+ [Fact]
+ public async Task UpsertAsync_PerSport_SavingNflNeverChangesCollege()
+ {
+ var db = nameof(UpsertAsync_PerSport_SavingNflNeverChangesCollege);
+ await SqliteTestDb.WithDb(db, async factory =>
+ {
+ await using (var seed = SqliteTestDb.Open(db))
+ await SqliteTestDb.SeedUser(seed, "alice");
+ var service = new NotificationPreferencesService(factory);
+
+ await service.UpsertAsync("alice", LeagueType.Cfb, new NotificationPreferencesDto { NotifyWeekResult = true });
+ await service.UpsertAsync("alice", LeagueType.Nfl, new NotificationPreferencesDto { NotifyWeekResult = false, NotifyMineBloodyDuringGame = true });
+
+ Assert.True((await service.GetAsync("alice", LeagueType.Cfb)).NotifyWeekResult);
+ Assert.False((await service.GetAsync("alice", LeagueType.Cfb)).NotifyMineBloodyDuringGame);
+ Assert.False((await service.GetAsync("alice", LeagueType.Nfl)).NotifyWeekResult);
+ Assert.True((await service.GetAsync("alice", LeagueType.Nfl)).NotifyMineBloodyDuringGame);
+ });
+ }
}
diff --git a/Server.UnitTests/NotificationsControllerTests.cs b/Server.UnitTests/NotificationsControllerTests.cs
index 25c31893..03467639 100644
--- a/Server.UnitTests/NotificationsControllerTests.cs
+++ b/Server.UnitTests/NotificationsControllerTests.cs
@@ -34,13 +34,28 @@ [new Claim(ClaimTypes.NameIdentifier, userId)], "Test")),
// ── Preferences ───────────────────────────────────────────────────────────
+ [Fact]
+ public async Task Preferences_AreReadAndSavedForTheRequestedSport()
+ {
+ var dto = new NotificationPreferencesDto { NotifyWeekResult = true };
+ _preferencesService.GetAsync("user-1", LeagueType.Cfb).Returns(dto);
+ _preferencesService.UpsertAsync("user-1", LeagueType.Cfb, dto).Returns(dto);
+
+ await BuildController("user-1").GetPreferences(LeagueType.Cfb);
+ await BuildController("user-1").PutPreferences(LeagueType.Cfb, dto);
+
+ await _preferencesService.Received(1).GetAsync("user-1", LeagueType.Cfb);
+ await _preferencesService.Received(1).UpsertAsync("user-1", LeagueType.Cfb, dto);
+ }
+
+
[Fact]
public async Task GetPreferences_ReturnsTheCallersPreferences()
{
var dto = new NotificationPreferencesDto { NotifyWeekResult = true };
- _preferencesService.GetAsync("user-1").Returns(dto);
+ _preferencesService.GetAsync("user-1", LeagueType.Nfl).Returns(dto);
- var result = await BuildController("user-1").GetPreferences();
+ var result = await BuildController("user-1").GetPreferences(LeagueType.Nfl);
var ok = Assert.IsType(result.Result);
Assert.Same(dto, ok.Value);
@@ -50,12 +65,12 @@ public async Task GetPreferences_ReturnsTheCallersPreferences()
public async Task PutPreferences_SavesForTheCaller_NeverAForeignUser()
{
var request = new NotificationPreferencesDto { NotifyMineCoveringAtFinal = true };
- _preferencesService.UpsertAsync("user-1", request).Returns(request);
+ _preferencesService.UpsertAsync("user-1", LeagueType.Nfl, request).Returns(request);
- var result = await BuildController("user-1").PutPreferences(request);
+ var result = await BuildController("user-1").PutPreferences(LeagueType.Nfl, request);
Assert.IsType(result.Result);
- await _preferencesService.Received(1).UpsertAsync("user-1", request);
+ await _preferencesService.Received(1).UpsertAsync("user-1", LeagueType.Nfl, request);
// No overload of UpsertAsync accepts a caller-supplied userId from the request body at
// all — the only userId that can ever reach the service is CurrentUserId.
}
diff --git a/Server/Controllers/NotificationsController.cs b/Server/Controllers/NotificationsController.cs
index 549f7afd..a753f657 100644
--- a/Server/Controllers/NotificationsController.cs
+++ b/Server/Controllers/NotificationsController.cs
@@ -1,3 +1,4 @@
+using FourPlayWebApp.Shared.Models.Enum;
using System.Security.Claims;
using FourPlayWebApp.Server.Auth;
using FourPlayWebApp.Server.Services;
@@ -20,15 +21,15 @@ public class NotificationsController(
private string CurrentUserId => User.FindFirstValue(ClaimTypes.NameIdentifier)!;
[HttpGet("preferences")]
- public async Task> GetPreferences()
+ public async Task> GetPreferences([FromQuery] LeagueType sport = LeagueType.Nfl)
{
- return Ok(await preferencesService.GetAsync(CurrentUserId));
+ return Ok(await preferencesService.GetAsync(CurrentUserId, sport));
}
[HttpPut("preferences")]
- public async Task> PutPreferences([FromBody] NotificationPreferencesDto preferences)
+ public async Task> PutPreferences([FromQuery] LeagueType sport, [FromBody] NotificationPreferencesDto preferences)
{
- return Ok(await preferencesService.UpsertAsync(CurrentUserId, preferences));
+ return Ok(await preferencesService.UpsertAsync(CurrentUserId, sport, preferences));
}
[HttpGet("vapid-public-key")]
diff --git a/Server/Data/Configurations/NotificationPreferencesConfiguration.cs b/Server/Data/Configurations/NotificationPreferencesConfiguration.cs
index 4b281cc3..02a53561 100644
--- a/Server/Data/Configurations/NotificationPreferencesConfiguration.cs
+++ b/Server/Data/Configurations/NotificationPreferencesConfiguration.cs
@@ -13,6 +13,6 @@ public void Configure(EntityTypeBuilder entity)
.HasColumnType("timestamptz")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
- entity.HasIndex(e => e.UserId).IsUnique();
+ entity.HasIndex(e => new { e.UserId, e.Sport }).IsUnique();
}
}
diff --git a/Server/Migrations/20261004204137_PerSportNotificationPreferences.Designer.cs b/Server/Migrations/20261004204137_PerSportNotificationPreferences.Designer.cs
new file mode 100644
index 00000000..131041c6
--- /dev/null
+++ b/Server/Migrations/20261004204137_PerSportNotificationPreferences.Designer.cs
@@ -0,0 +1,1516 @@
+//
+using System;
+using FourPlayWebApp.Server.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace FourPlayWebApp.Server.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20261004204137_PerSportNotificationPreferences")]
+ partial class PerSportNotificationPreferences
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.CfbRanking", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CapturedAtUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CuratedRank")
+ .HasColumnType("integer");
+
+ b.Property("EspnEventId")
+ .HasColumnType("integer");
+
+ b.Property("EspnWeekNumber")
+ .HasColumnType("integer");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("TeamAbbreviation")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Season", "EspnWeekNumber", "TeamAbbreviation")
+ .IsUnique();
+
+ b.ToTable("CfbRankings");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.CfbSeasonWeekConfig", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("EspnWeekNumber")
+ .HasColumnType("integer");
+
+ b.Property("InScopeIvLeague")
+ .HasColumnType("boolean");
+
+ b.Property("IvLeagueWeekNumber")
+ .HasColumnType("integer");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("ScoringFormat")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("SpreadLockDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WeekEndDate")
+ .HasColumnType("date");
+
+ b.Property("WeekStartDate")
+ .HasColumnType("date");
+
+ b.Property("WeekType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Season", "EspnWeekNumber")
+ .IsUnique();
+
+ b.HasIndex("Season", "IvLeagueWeekNumber")
+ .IsUnique()
+ .HasFilter("\"IvLeagueWeekNumber\" <> 99");
+
+ b.ToTable("CfbSeasonWeekConfigs");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.CfbSlates", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("EndDate")
+ .HasColumnType("date");
+
+ b.Property("EspnWeekNumber")
+ .HasColumnType("integer");
+
+ b.Property("FirstGameUtc")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Label")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ScoringFormat")
+ .HasColumnType("text");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("SlateNumber")
+ .HasColumnType("integer");
+
+ b.Property("SlateType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StartDate")
+ .HasColumnType("date");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Season", "SlateNumber")
+ .IsUnique();
+
+ b.ToTable("CfbSlates");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.LeagueInfo", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("LeagueName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("LeagueType")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(0);
+
+ b.Property("OwnerUserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LeagueName")
+ .IsUnique();
+
+ b.HasIndex("OwnerUserId");
+
+ b.ToTable("LeagueInfo");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.LeagueInviteLink", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedByUserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsRevoked")
+ .HasColumnType("boolean");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedByUserId");
+
+ b.HasIndex("LeagueId");
+
+ b.HasIndex("Token")
+ .IsUnique();
+
+ b.ToTable("LeagueInviteLinks");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.LeagueJuiceMapping", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("Juice")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(13);
+
+ b.Property("JuiceConference")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(6);
+
+ b.Property("JuiceDivisional")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(10);
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("StartWeek")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(1);
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WeeklyCost")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasDefaultValue(5);
+
+ b.HasKey("Id");
+
+ b.HasIndex("LeagueId", "Season")
+ .IsUnique();
+
+ b.ToTable("LeagueJuiceMapping");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.LeagueJuiceReminderSent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("SentAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LeagueId", "Season")
+ .IsUnique();
+
+ b.ToTable("LeagueJuiceReminderSent");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.LeagueMembershipInvite", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("InvitedByUserId")
+ .HasColumnType("text");
+
+ b.Property("InvitedUserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("RespondedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InvitedByUserId");
+
+ b.HasIndex("InvitedUserId");
+
+ b.HasIndex("LeagueId", "InvitedUserId")
+ .IsUnique();
+
+ b.ToTable("LeagueMembershipInvites");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.LeagueUserMapping", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("RemovedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("LeagueId", "UserId")
+ .IsUnique();
+
+ b.ToTable("LeagueUserMapping");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.NflPicks", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("NflWeek")
+ .HasColumnType("integer");
+
+ b.Property("Pick")
+ .HasColumnType("integer");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("Team")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LeagueId");
+
+ b.HasIndex("UserId", "LeagueId", "NflWeek", "Season", "Team", "Pick")
+ .IsUnique();
+
+ b.ToTable("NflPicks");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.NflSeasonWeekConfig", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FirstGameOfWeekStartDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ScoringFormat")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("SpreadLockDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WeekEndDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WeekId")
+ .HasColumnType("integer");
+
+ b.Property("WeekLabel")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("WeekNotes")
+ .HasColumnType("text");
+
+ b.Property("WeekStartDatetime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("WeekType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("NflSeasonWeekConfigs");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.NflWeeks", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("EndDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("NflWeek")
+ .HasColumnType("integer");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("StartDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Season", "NflWeek")
+ .IsUnique();
+
+ b.ToTable("NflWeeks");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.NotificationPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("NotifyMineBloodyAtFinal")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyMineBloodyDuringGame")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyMineCoveringAtFinal")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyMineCoveringDuringGame")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyOthersBloodyAtFinal")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyOthersBloodyDuringGame")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyOthersCoveringAtFinal")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyOthersCoveringDuringGame")
+ .HasColumnType("boolean");
+
+ b.Property("NotifyWeekResult")
+ .HasColumnType("boolean");
+
+ b.Property("Sport")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "Sport")
+ .IsUnique();
+
+ b.ToTable("NotificationPreferences");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.PickLiveNotificationState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FinalNotifiedAt")
+ .HasColumnType("timestamptz");
+
+ b.Property("LastNotifiedAt")
+ .HasColumnType("timestamptz");
+
+ b.Property("LastNotifiedCovering")
+ .HasColumnType("boolean");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("PickId")
+ .HasColumnType("integer");
+
+ b.Property("Sport")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LeagueId");
+
+ b.HasIndex("Sport", "PickId")
+ .IsUnique();
+
+ b.ToTable("PickLiveNotificationStates");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.PushSubscription", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Auth")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("timestamptz");
+
+ b.Property("P256dh")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Sport")
+ .HasColumnType("integer");
+
+ b.Property("UserAgent")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Endpoint")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("PushSubscriptions");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.TeamLiveNotificationState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FinalNotifiedAt")
+ .HasColumnType("timestamptz");
+
+ b.Property("LastNotifiedAt")
+ .HasColumnType("timestamptz");
+
+ b.Property("LastNotifiedCovering")
+ .HasColumnType("boolean");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("Period")
+ .HasColumnType("integer");
+
+ b.Property("PickType")
+ .HasColumnType("integer");
+
+ b.Property("Sport")
+ .HasColumnType("integer");
+
+ b.Property("Team")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Sport", "LeagueId", "Team", "PickType", "Period")
+ .IsUnique();
+
+ b.ToTable("TeamLiveNotificationStates");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Data.WeekResultNotificationSent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("SentAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Week")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "LeagueId", "Season", "Week")
+ .IsUnique();
+
+ b.ToTable("WeekResultNotificationSent");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Identity.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("EnableEmails")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("text");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Identity.RefreshToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Revoked")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Token")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.ToTable("RefreshTokens");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Server.Models.Invitation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("InvitationCode")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("InvitedByUserId")
+ .HasColumnType("text");
+
+ b.Property("IsUsed")
+ .HasColumnType("boolean");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("RegisteredUserId")
+ .HasColumnType("text");
+
+ b.Property("UsedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Email")
+ .IsUnique()
+ .HasFilter("\"LeagueId\" IS NULL");
+
+ b.HasIndex("InvitedByUserId");
+
+ b.HasIndex("LeagueId");
+
+ b.HasIndex("RegisteredUserId");
+
+ b.HasIndex("Email", "LeagueId")
+ .IsUnique();
+
+ b.ToTable("Invitations");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Shared.Models.Data.CfbPicks", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CfbSlateId")
+ .HasColumnType("integer");
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("LeagueId")
+ .HasColumnType("integer");
+
+ b.Property("PickType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Season")
+ .HasColumnType("integer");
+
+ b.Property("Team")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CfbSlateId");
+
+ b.HasIndex("LeagueId");
+
+ b.HasIndex("UserId", "LeagueId", "CfbSlateId", "Season", "Team", "PickType")
+ .IsUnique();
+
+ b.ToTable("CfbPicks");
+ });
+
+ modelBuilder.Entity("FourPlayWebApp.Shared.Models.Data.CfbScores", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AwayTeam")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("AwayTeamScore")
+ .HasColumnType("integer");
+
+ b.Property("CfbSlateId")
+ .HasColumnType("integer");
+
+ b.Property("DateCreated")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamptz")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("GameStatus")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("GameTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property