feat: Add new API controllers for progression events, room consumables, currencies, earnings distributions, screen share reports, subscription seasons, and UGC purchasables

- Implemented ProgressionEventsController to handle progression event retrieval and active events.
- Created RoomConsumablesController for listing and purchasing room consumables.
- Developed RoomCurrenciesController for managing room currencies, including creation, updates, and balance retrieval.
- Added RoomEarningsDistributionsController to fetch earnings distribution data for rooms.
- Introduced ScreenShareReportsController for reporting screen share incidents.
- Established SubscriptionSeasonsController to provide current subscription season details and rewards.
- Launched UgcPurchasablesController for managing user-generated content purchasables, including bulk operations.
- Added new data entities for customization and room economy, including custom avatar items, room currencies, and UGC purchasables.
This commit is contained in:
Alexa
2026-06-22 16:08:23 +03:00
parent 157cc457a9
commit 70980bf3b6
53 changed files with 4470 additions and 91 deletions
+321
View File
@@ -104,6 +104,8 @@ public static class DatabaseBootstrap
await EnsureInventorySchemaAsync(db);
await EnsureLeaderboardSchemaAsync(db);
await EnsureRoomColumnsAsync(db);
await EnsureInventionColumnsAsync(db);
await EnsureMarch2023TablesAsync(db);
// Coach system account at Player.Id=1. The RR-Original room seeder
// below assigns CreatorPlayerId=1 to every canonical room
@@ -251,6 +253,12 @@ public static class DatabaseBootstrap
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN IF NOT EXISTS ""MaxCapacity"" integer NOT NULL DEFAULT 8;");
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN IF NOT EXISTS ""IsStudioRoom"" boolean NOT NULL DEFAULT false;");
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN IF NOT EXISTS ""IsRoomLinkedToRecRoomStudio"" boolean NOT NULL DEFAULT false;");
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN IF NOT EXISTS ""StudioSessionId"" character varying(128) NOT NULL DEFAULT '';");
return;
}
@@ -262,6 +270,45 @@ public static class DatabaseBootstrap
@"ALTER TABLE ""Rooms"" ADD COLUMN ""MaxCapacity"" INTEGER NOT NULL DEFAULT 8;");
}
catch { }
try
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN ""IsStudioRoom"" INTEGER NOT NULL DEFAULT 0;");
}
catch { }
try
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN ""IsRoomLinkedToRecRoomStudio"" INTEGER NOT NULL DEFAULT 0;");
}
catch { }
try
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Rooms"" ADD COLUMN ""StudioSessionId"" TEXT NOT NULL DEFAULT '';");
}
catch { }
}
private static async Task EnsureInventionColumnsAsync(DorkNetDbContext db)
{
var provider = db.Database.ProviderName ?? string.Empty;
if (provider.Contains("Npgsql", StringComparison.OrdinalIgnoreCase))
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Inventions"" ADD COLUMN IF NOT EXISTS ""Price"" integer NOT NULL DEFAULT 0;");
return;
}
try
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""Inventions"" ADD COLUMN ""Price"" INTEGER NOT NULL DEFAULT 0;");
}
catch { }
}
private static async Task EnsureServerSettingsColumnsAsync(DorkNetDbContext db)
@@ -464,6 +511,280 @@ public static class DatabaseBootstrap
@"CREATE INDEX IF NOT EXISTS ""IX_LeaderboardStats_RoomId_StatChannel_Value"" ON ""LeaderboardStats"" (""RoomId"", ""StatChannel"", ""Value"");");
}
private static async Task EnsureMarch2023TablesAsync(DorkNetDbContext db)
{
var provider = db.Database.ProviderName ?? string.Empty;
if (provider.Contains("Npgsql", StringComparison.OrdinalIgnoreCase))
{
var statements = new[]
{
"""
CREATE TABLE IF NOT EXISTS "CustomAvatarItems" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PublicId" uuid NOT NULL,
"CreatorPlayerId" bigint NOT NULL,
"Name" character varying(128) NOT NULL,
"Description" character varying(1024) NOT NULL,
"Price" integer NOT NULL DEFAULT 100,
"ItemType" integer NOT NULL DEFAULT 0,
"BaseAvatarItemId" integer NOT NULL DEFAULT 0,
"Color" character varying(64) NOT NULL DEFAULT '',
"ImageName" character varying(256) NOT NULL DEFAULT '',
"AssetName" character varying(256) NOT NULL DEFAULT '',
"IsPublic" boolean NOT NULL DEFAULT false,
"IsFeatured" boolean NOT NULL DEFAULT false,
"CheerCount" bigint NOT NULL DEFAULT 0,
"PurchaseCount" bigint NOT NULL DEFAULT 0,
"CreatedAt" timestamp with time zone NOT NULL DEFAULT now(),
"UpdatedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_PublicId"" ON ""CustomAvatarItems"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_CreatorPlayerId"" ON ""CustomAvatarItems"" (""CreatorPlayerId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_IsPublic"" ON ""CustomAvatarItems"" (""IsPublic"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_IsFeatured"" ON ""CustomAvatarItems"" (""IsFeatured"");",
"""
CREATE TABLE IF NOT EXISTS "CustomAvatarItemOwnership" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PlayerId" bigint NOT NULL,
"CustomAvatarItemId" bigint NOT NULL,
"AcquiredAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_CustomAvatarItemOwnership_PlayerId_CustomAvatarItemId"" ON ""CustomAvatarItemOwnership"" (""PlayerId"", ""CustomAvatarItemId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItemOwnership_CustomAvatarItemId"" ON ""CustomAvatarItemOwnership"" (""CustomAvatarItemId"");",
"""
CREATE TABLE IF NOT EXISTS "ItemWishlists" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PlayerId" bigint NOT NULL,
"ItemKey" character varying(128) NOT NULL,
"ItemType" integer NOT NULL DEFAULT 0,
"CreatedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_ItemWishlists_PlayerId_ItemKey_ItemType"" ON ""ItemWishlists"" (""PlayerId"", ""ItemKey"", ""ItemType"");",
"""
CREATE TABLE IF NOT EXISTS "Keepsakes" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PlayerId" bigint NOT NULL,
"Category" character varying(64) NOT NULL DEFAULT 'general',
"EventKey" character varying(128) NOT NULL DEFAULT '',
"Title" character varying(128) NOT NULL DEFAULT '',
"Description" character varying(1024) NOT NULL DEFAULT '',
"ImageName" character varying(256) NOT NULL DEFAULT '',
"EarnedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE INDEX IF NOT EXISTS ""IX_Keepsakes_PlayerId"" ON ""Keepsakes"" (""PlayerId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_Keepsakes_PlayerId_EventKey"" ON ""Keepsakes"" (""PlayerId"", ""EventKey"");",
"""
CREATE TABLE IF NOT EXISTS "RoomCurrencies" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PublicId" uuid NOT NULL,
"RoomId" bigint NOT NULL,
"CreatorPlayerId" bigint NOT NULL,
"Name" character varying(64) NOT NULL DEFAULT '',
"Description" character varying(512) NOT NULL DEFAULT '',
"ImageName" character varying(256) NOT NULL DEFAULT '',
"DailyLimit" integer NOT NULL DEFAULT 0,
"IsDeleted" boolean NOT NULL DEFAULT false,
"CreatedAt" timestamp with time zone NOT NULL DEFAULT now(),
"UpdatedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_RoomCurrencies_PublicId"" ON ""RoomCurrencies"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencies_RoomId"" ON ""RoomCurrencies"" (""RoomId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencies_CreatorPlayerId"" ON ""RoomCurrencies"" (""CreatorPlayerId"");",
"""
CREATE TABLE IF NOT EXISTS "RoomCurrencyBalances" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PlayerId" bigint NOT NULL,
"RoomCurrencyId" bigint NOT NULL,
"Balance" bigint NOT NULL DEFAULT 0,
"UpdatedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_RoomCurrencyBalances_PlayerId_RoomCurrencyId"" ON ""RoomCurrencyBalances"" (""PlayerId"", ""RoomCurrencyId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencyBalances_RoomCurrencyId"" ON ""RoomCurrencyBalances"" (""RoomCurrencyId"");",
"""
CREATE TABLE IF NOT EXISTS "RoomCurrencyPurchaseOffers" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PublicId" uuid NOT NULL,
"RoomCurrencyId" bigint NOT NULL,
"Name" character varying(64) NOT NULL DEFAULT '',
"Amount" integer NOT NULL DEFAULT 0,
"Price" integer NOT NULL DEFAULT 0,
"CurrencyType" integer NOT NULL DEFAULT 2,
"IsDeleted" boolean NOT NULL DEFAULT false,
"CreatedAt" timestamp with time zone NOT NULL DEFAULT now(),
"UpdatedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_RoomCurrencyPurchaseOffers_PublicId"" ON ""RoomCurrencyPurchaseOffers"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencyPurchaseOffers_RoomCurrencyId"" ON ""RoomCurrencyPurchaseOffers"" (""RoomCurrencyId"");",
"""
CREATE TABLE IF NOT EXISTS "UgcPurchasables" (
"Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"PublicId" uuid NOT NULL,
"RoomId" bigint NOT NULL,
"CreatorPlayerId" bigint NOT NULL,
"Name" character varying(128) NOT NULL DEFAULT '',
"Description" character varying(1024) NOT NULL DEFAULT '',
"ImageName" character varying(256) NOT NULL DEFAULT '',
"Price" integer NOT NULL DEFAULT 0,
"CurrencyType" integer NOT NULL DEFAULT 2,
"ItemType" integer NOT NULL DEFAULT 0,
"IsFeatured" boolean NOT NULL DEFAULT false,
"SortOrder" integer NOT NULL DEFAULT 0,
"IsDeleted" boolean NOT NULL DEFAULT false,
"CreatedAt" timestamp with time zone NOT NULL DEFAULT now(),
"UpdatedAt" timestamp with time zone NOT NULL DEFAULT now()
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_UgcPurchasables_PublicId"" ON ""UgcPurchasables"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_UgcPurchasables_RoomId"" ON ""UgcPurchasables"" (""RoomId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_UgcPurchasables_CreatorPlayerId"" ON ""UgcPurchasables"" (""CreatorPlayerId"");",
};
foreach (var sql in statements)
await db.Database.ExecuteSqlRawAsync(sql);
return;
}
var sqliteStatements = new[]
{
"""
CREATE TABLE IF NOT EXISTS "CustomAvatarItems" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_CustomAvatarItems" PRIMARY KEY AUTOINCREMENT,
"PublicId" TEXT NOT NULL,
"CreatorPlayerId" INTEGER NOT NULL,
"Name" TEXT NOT NULL,
"Description" TEXT NOT NULL,
"Price" INTEGER NOT NULL DEFAULT 100,
"ItemType" INTEGER NOT NULL DEFAULT 0,
"BaseAvatarItemId" INTEGER NOT NULL DEFAULT 0,
"Color" TEXT NOT NULL DEFAULT '',
"ImageName" TEXT NOT NULL DEFAULT '',
"AssetName" TEXT NOT NULL DEFAULT '',
"IsPublic" INTEGER NOT NULL DEFAULT 0,
"IsFeatured" INTEGER NOT NULL DEFAULT 0,
"CheerCount" INTEGER NOT NULL DEFAULT 0,
"PurchaseCount" INTEGER NOT NULL DEFAULT 0,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_PublicId"" ON ""CustomAvatarItems"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_CreatorPlayerId"" ON ""CustomAvatarItems"" (""CreatorPlayerId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_IsPublic"" ON ""CustomAvatarItems"" (""IsPublic"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItems_IsFeatured"" ON ""CustomAvatarItems"" (""IsFeatured"");",
"""
CREATE TABLE IF NOT EXISTS "CustomAvatarItemOwnership" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_CustomAvatarItemOwnership" PRIMARY KEY AUTOINCREMENT,
"PlayerId" INTEGER NOT NULL,
"CustomAvatarItemId" INTEGER NOT NULL,
"AcquiredAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_CustomAvatarItemOwnership_PlayerId_CustomAvatarItemId"" ON ""CustomAvatarItemOwnership"" (""PlayerId"", ""CustomAvatarItemId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_CustomAvatarItemOwnership_CustomAvatarItemId"" ON ""CustomAvatarItemOwnership"" (""CustomAvatarItemId"");",
"""
CREATE TABLE IF NOT EXISTS "ItemWishlists" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_ItemWishlists" PRIMARY KEY AUTOINCREMENT,
"PlayerId" INTEGER NOT NULL,
"ItemKey" TEXT NOT NULL,
"ItemType" INTEGER NOT NULL DEFAULT 0,
"CreatedAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_ItemWishlists_PlayerId_ItemKey_ItemType"" ON ""ItemWishlists"" (""PlayerId"", ""ItemKey"", ""ItemType"");",
"""
CREATE TABLE IF NOT EXISTS "Keepsakes" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Keepsakes" PRIMARY KEY AUTOINCREMENT,
"PlayerId" INTEGER NOT NULL,
"Category" TEXT NOT NULL DEFAULT 'general',
"EventKey" TEXT NOT NULL DEFAULT '',
"Title" TEXT NOT NULL DEFAULT '',
"Description" TEXT NOT NULL DEFAULT '',
"ImageName" TEXT NOT NULL DEFAULT '',
"EarnedAt" TEXT NOT NULL
);
""",
@"CREATE INDEX IF NOT EXISTS ""IX_Keepsakes_PlayerId"" ON ""Keepsakes"" (""PlayerId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_Keepsakes_PlayerId_EventKey"" ON ""Keepsakes"" (""PlayerId"", ""EventKey"");",
"""
CREATE TABLE IF NOT EXISTS "RoomCurrencies" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_RoomCurrencies" PRIMARY KEY AUTOINCREMENT,
"PublicId" TEXT NOT NULL,
"RoomId" INTEGER NOT NULL,
"CreatorPlayerId" INTEGER NOT NULL,
"Name" TEXT NOT NULL DEFAULT '',
"Description" TEXT NOT NULL DEFAULT '',
"ImageName" TEXT NOT NULL DEFAULT '',
"DailyLimit" INTEGER NOT NULL DEFAULT 0,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_RoomCurrencies_PublicId"" ON ""RoomCurrencies"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencies_RoomId"" ON ""RoomCurrencies"" (""RoomId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencies_CreatorPlayerId"" ON ""RoomCurrencies"" (""CreatorPlayerId"");",
"""
CREATE TABLE IF NOT EXISTS "RoomCurrencyBalances" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_RoomCurrencyBalances" PRIMARY KEY AUTOINCREMENT,
"PlayerId" INTEGER NOT NULL,
"RoomCurrencyId" INTEGER NOT NULL,
"Balance" INTEGER NOT NULL DEFAULT 0,
"UpdatedAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_RoomCurrencyBalances_PlayerId_RoomCurrencyId"" ON ""RoomCurrencyBalances"" (""PlayerId"", ""RoomCurrencyId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencyBalances_RoomCurrencyId"" ON ""RoomCurrencyBalances"" (""RoomCurrencyId"");",
"""
CREATE TABLE IF NOT EXISTS "RoomCurrencyPurchaseOffers" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_RoomCurrencyPurchaseOffers" PRIMARY KEY AUTOINCREMENT,
"PublicId" TEXT NOT NULL,
"RoomCurrencyId" INTEGER NOT NULL,
"Name" TEXT NOT NULL DEFAULT '',
"Amount" INTEGER NOT NULL DEFAULT 0,
"Price" INTEGER NOT NULL DEFAULT 0,
"CurrencyType" INTEGER NOT NULL DEFAULT 2,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_RoomCurrencyPurchaseOffers_PublicId"" ON ""RoomCurrencyPurchaseOffers"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_RoomCurrencyPurchaseOffers_RoomCurrencyId"" ON ""RoomCurrencyPurchaseOffers"" (""RoomCurrencyId"");",
"""
CREATE TABLE IF NOT EXISTS "UgcPurchasables" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_UgcPurchasables" PRIMARY KEY AUTOINCREMENT,
"PublicId" TEXT NOT NULL,
"RoomId" INTEGER NOT NULL,
"CreatorPlayerId" INTEGER NOT NULL,
"Name" TEXT NOT NULL DEFAULT '',
"Description" TEXT NOT NULL DEFAULT '',
"ImageName" TEXT NOT NULL DEFAULT '',
"Price" INTEGER NOT NULL DEFAULT 0,
"CurrencyType" INTEGER NOT NULL DEFAULT 2,
"ItemType" INTEGER NOT NULL DEFAULT 0,
"IsFeatured" INTEGER NOT NULL DEFAULT 0,
"SortOrder" INTEGER NOT NULL DEFAULT 0,
"IsDeleted" INTEGER NOT NULL DEFAULT 0,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
""",
@"CREATE UNIQUE INDEX IF NOT EXISTS ""IX_UgcPurchasables_PublicId"" ON ""UgcPurchasables"" (""PublicId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_UgcPurchasables_RoomId"" ON ""UgcPurchasables"" (""RoomId"");",
@"CREATE INDEX IF NOT EXISTS ""IX_UgcPurchasables_CreatorPlayerId"" ON ""UgcPurchasables"" (""CreatorPlayerId"");",
};
foreach (var sql in sqliteStatements)
await db.Database.ExecuteSqlRawAsync(sql);
}
/// <summary>SQLite migration-history reconciliation. Handles two
/// cutover scenarios so legacy DBs boot cleanly without manual SQL:
/// <list type="number">