Add team roles and content management updates

This commit is contained in:
AzuTear
2026-06-25 19:52:46 +02:00
parent 54293f4c45
commit 693769e5a8
126 changed files with 6966 additions and 738 deletions
+28
View File
@@ -17,6 +17,8 @@ public sealed class AwardsDbContext(DbContextOptions<AwardsDbContext> options) :
public DbSet<AdminAuditEntry> AdminAuditEntries => Set<AdminAuditEntry>();
public DbSet<ClipSubmission> ClipSubmissions => Set<ClipSubmission>();
public DbSet<SiteSettings> SiteSettings => Set<SiteSettings>();
public DbSet<TeamMember> TeamMembers => Set<TeamMember>();
public DbSet<TeamRolePermission> TeamRolePermissions => Set<TeamRolePermission>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -43,10 +45,36 @@ public sealed class AwardsDbContext(DbContextOptions<AwardsDbContext> options) :
entity.Property(item => item.DemoLoginPasswordSalt).HasMaxLength(80);
entity.Property(item => item.DemoLoginTwitchUserId).HasMaxLength(120);
entity.Property(item => item.DemoLoginDisplayName).HasMaxLength(120);
entity.Property(item => item.TwitchClientId).HasMaxLength(120);
entity.Property(item => item.TwitchClientSecret).HasMaxLength(180);
entity.Property(item => item.TwitchRedirectUri).HasMaxLength(400);
entity.Property(item => item.TwitchScope).HasMaxLength(300);
entity.Property(item => item.MaintenanceTitle).HasMaxLength(120);
entity.Property(item => item.MaintenanceMessage).HasMaxLength(600);
});
modelBuilder.Entity<TeamMember>(entity =>
{
entity.HasIndex(item => item.Login).IsUnique();
entity.HasIndex(item => item.BoundTwitchUserId).IsUnique();
entity.Property(item => item.Login).HasMaxLength(80);
entity.Property(item => item.DisplayName).HasMaxLength(120);
entity.Property(item => item.Role).HasMaxLength(40);
entity.Property(item => item.PasswordHash).HasMaxLength(120);
entity.Property(item => item.PasswordSalt).HasMaxLength(80);
entity.Property(item => item.BoundTwitchUserId).HasMaxLength(120);
entity.Property(item => item.BoundTwitchDisplayName).HasMaxLength(120);
entity.Property(item => item.CreatedByTwitchId).HasMaxLength(120);
entity.Property(item => item.UpdatedByTwitchId).HasMaxLength(120);
});
modelBuilder.Entity<TeamRolePermission>(entity =>
{
entity.HasIndex(item => item.Role).IsUnique();
entity.Property(item => item.Role).HasMaxLength(40);
entity.Property(item => item.UpdatedByTwitchId).HasMaxLength(120);
});
modelBuilder.Entity<Category>(entity =>
{
entity.HasIndex(item => new { item.SeasonId, item.Slug }).IsUnique();
@@ -16,6 +16,21 @@ public static class OperationalTablesBootstrapper
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "RiskRulesJson" text NOT NULL DEFAULT '[]';
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "TwitchAuthManagedByDatabase" boolean NOT NULL DEFAULT false;
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "TwitchClientId" character varying(120) NOT NULL DEFAULT '';
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "TwitchClientSecret" character varying(180) NOT NULL DEFAULT '';
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "TwitchRedirectUri" character varying(400) NOT NULL DEFAULT '';
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "TwitchScope" character varying(300) NOT NULL DEFAULT '';
CREATE TABLE IF NOT EXISTS "RiskFlags" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"SeasonId" integer NULL,
@@ -128,5 +143,52 @@ public static class OperationalTablesBootstrapper
CREATE INDEX IF NOT EXISTS "IX_Nominations_SeasonId_Status"
ON "Nominations" ("SeasonId", "Status");
CREATE TABLE IF NOT EXISTS "TeamMembers" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"Login" character varying(80) NOT NULL,
"DisplayName" character varying(120) NOT NULL,
"Role" character varying(40) NOT NULL,
"PasswordHash" character varying(120) NOT NULL,
"PasswordSalt" character varying(80) NOT NULL,
"BoundTwitchUserId" character varying(120) NULL,
"BoundTwitchDisplayName" character varying(120) NULL,
"MustChangePassword" boolean NOT NULL,
"IsActive" boolean NOT NULL,
"CreatedByTwitchId" character varying(120) NOT NULL,
"UpdatedByTwitchId" character varying(120) NULL,
"CreatedAt" timestamp with time zone NOT NULL,
"UpdatedAt" timestamp with time zone NULL,
"LastLoginAt" timestamp with time zone NULL,
"TwitchBoundAt" timestamp with time zone NULL,
"PasswordResetAt" timestamp with time zone NULL
);
ALTER TABLE "TeamMembers"
ADD COLUMN IF NOT EXISTS "BoundTwitchUserId" character varying(120) NULL;
ALTER TABLE "TeamMembers"
ADD COLUMN IF NOT EXISTS "BoundTwitchDisplayName" character varying(120) NULL;
ALTER TABLE "TeamMembers"
ADD COLUMN IF NOT EXISTS "TwitchBoundAt" timestamp with time zone NULL;
CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeamMembers_Login"
ON "TeamMembers" ("Login");
CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeamMembers_BoundTwitchUserId"
ON "TeamMembers" ("BoundTwitchUserId")
WHERE "BoundTwitchUserId" IS NOT NULL;
CREATE TABLE IF NOT EXISTS "TeamRolePermissions" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"Role" character varying(40) NOT NULL,
"PermissionsJson" text NOT NULL,
"UpdatedByTwitchId" character varying(120) NOT NULL,
"UpdatedAt" timestamp with time zone NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeamRolePermissions_Role"
ON "TeamRolePermissions" ("Role");
""");
}
+29
View File
@@ -55,6 +55,35 @@ internal static class SeedCatalog
new("Discord", "discord", "https://discord.gg/jayuhime", "discord"),
];
internal const string DefaultImprintContent = """
Anbieter
VTuber Star Awards, vertreten durch Jayuhime.
Kontakt
Nutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.
Hinweis
Dieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.
""";
internal const string DefaultContactContent = """
Kontakt zum Award-Team
Du hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.
Datenschutzfragen
Fuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.
Community & Kooperationen
Social Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.
""";
internal const string DefaultSponsorsContent = """
Sponsoren & Partner
Hier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.
Partner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.
""";
internal static readonly CandidateSeed[] CurrentCandidateSeeds =
[
new("vtuber-des-jahres", "Hoshimi Miyu", "@hoshimimiyu", "Twitch"),
+3
View File
@@ -43,8 +43,11 @@ Keine Weitergabe zu Werbezwecken. Technische Dienstleister verarbeiten Daten aus
PrivacyPolicyUpdatedBy = "seed",
PrivacyPolicyUpdatedAt = new DateTimeOffset(2026, 6, 23, 0, 0, 0, TimeSpan.Zero),
ImprintUrl = "https://vtuber-star-awards.de/impressum",
ImprintContent = SeedCatalog.DefaultImprintContent,
ContactUrl = "https://vtuber-star-awards.de/kontakt",
ContactContent = SeedCatalog.DefaultContactContent,
SponsorsUrl = "https://vtuber-star-awards.de/partner",
SponsorsContent = SeedCatalog.DefaultSponsorsContent,
RiskRulesJson = RiskRuleSettings.Serialize(RiskRuleSettings.Defaults),
SocialLinksJson = JsonSerializer.Serialize(new[]
{
@@ -40,6 +40,21 @@ public static partial class SeedDataBootstrapper
{
settings.RiskRulesJson = RiskRuleSettings.Serialize(RiskRuleSettings.Defaults);
}
if (string.IsNullOrWhiteSpace(settings.ImprintContent))
{
settings.ImprintContent = SeedCatalog.DefaultImprintContent;
}
if (string.IsNullOrWhiteSpace(settings.ContactContent))
{
settings.ContactContent = SeedCatalog.DefaultContactContent;
}
if (string.IsNullOrWhiteSpace(settings.SponsorsContent))
{
settings.SponsorsContent = SeedCatalog.DefaultSponsorsContent;
}
}
private static bool HasValidSiteArray(string? json, params string[] requiredKeys)