add full-screen gift effects

This commit is contained in:
2026-07-19 00:39:56 -07:00
parent f79852d8e6
commit c4eca7b8bf
23 changed files with 1666 additions and 33 deletions
+23 -6
View File
@@ -15,6 +15,7 @@ use uuid::Uuid;
use crate::{
components::{ComponentInstance, ComponentRegistry},
db::{ComponentRecord, Db, DbError},
gift_effect::{GIFT_EFFECT_KIND, GIFT_EFFECT_NAME, GiftEffectSettings},
i18n,
realtime::InMemoryComponentStore,
song_request::{SONG_REQUEST_KIND, SONG_REQUEST_NAME, SongRequestSettings},
@@ -47,10 +48,10 @@ impl TenantRepository {
Ok(())
}
/// Ensure every active tenant has the built-in singleton song component.
/// The partial unique index makes this safe across concurrent application
/// starts; the state row is repaired independently for existing instances.
pub async fn ensure_song_request_components(&self) -> Result<(), RepositoryError> {
/// Ensure every active tenant has every required singleton component.
/// Partial unique indexes make concurrent starts idempotent. The song
/// component additionally owns a relational revision state row.
pub async fn ensure_builtin_components(&self) -> Result<(), RepositoryError> {
for tenant in self.db.list_active_tenants().await? {
let mut client = self.db.get().await?;
let transaction = client.transaction().await?;
@@ -96,6 +97,22 @@ impl TenantRepository {
&[&tenant.user_id, &component_id],
)
.await?;
let gift_settings = serde_json::to_value(GiftEffectSettings::default())
.map_err(|error| RepositoryError::Invalid(error.to_string()))?;
transaction
.execute(
"INSERT INTO component_instances \
(id,owner_user_id,kind,name,settings,settings_version,enabled) \
VALUES($1,$2,$3,$4,$5,1,true) ON CONFLICT DO NOTHING",
&[
&Uuid::new_v4(),
&tenant.user_id,
&GIFT_EFFECT_KIND,
&GIFT_EFFECT_NAME,
&gift_settings,
],
)
.await?;
transaction.commit().await?;
}
Ok(())
@@ -131,7 +148,7 @@ impl TenantRepository {
// Every tenant receives this singleton during registration/startup.
// Keeping creation internal prevents a second instance from racing the
// partial unique index and turning a domain conflict into a DB error.
if kind == SONG_REQUEST_KIND {
if matches!(kind, SONG_REQUEST_KIND | GIFT_EFFECT_KIND) {
return Err(RepositoryError::Forbidden);
}
let runtime = self
@@ -192,7 +209,7 @@ impl TenantRepository {
.await?
.ok_or(RepositoryError::NotFound)?
.get(0);
if kind == SONG_REQUEST_KIND {
if matches!(kind.as_str(), SONG_REQUEST_KIND | GIFT_EFFECT_KIND) {
return Err(RepositoryError::Forbidden);
}
let changed = transaction