old backend core lib

This commit is contained in:
2026-07-18 11:44:58 -07:00
parent 994854d104
commit f599672a30
36 changed files with 3776 additions and 276 deletions
+73
View File
@@ -16,6 +16,7 @@ use crate::{
components::{ComponentInstance, ComponentRegistry},
db::{ComponentRecord, Db, DbError},
realtime::InMemoryComponentStore,
song_request::{SONG_REQUEST_KIND, SONG_REQUEST_NAME, SongRequestSettings},
};
#[derive(Clone)]
@@ -45,6 +46,61 @@ 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> {
for tenant in self.db.list_active_tenants().await? {
let mut client = self.db.get().await?;
let transaction = client.transaction().await?;
Db::set_tenant(&transaction, tenant.user_id).await?;
let existing = transaction
.query_opt(
"SELECT id FROM component_instances WHERE owner_user_id=$1 AND kind=$2",
&[&tenant.user_id, &SONG_REQUEST_KIND],
)
.await?;
let component_id: Uuid = if let Some(row) = existing {
row.get(0)
} else {
let id = Uuid::new_v4();
let settings = serde_json::to_value(SongRequestSettings::default())
.map_err(|error| RepositoryError::Invalid(error.to_string()))?;
transaction
.execute(
"INSERT INTO component_instances \
(id,owner_user_id,source_id,kind,name,settings,settings_version,enabled) \
VALUES($1,$2,$3,$4,$5,$6,1,true) ON CONFLICT DO NOTHING",
&[
&id,
&tenant.user_id,
&tenant.source_id,
&SONG_REQUEST_KIND,
&SONG_REQUEST_NAME,
&settings,
],
)
.await?;
transaction
.query_one(
"SELECT id FROM component_instances WHERE owner_user_id=$1 AND kind=$2",
&[&tenant.user_id, &SONG_REQUEST_KIND],
)
.await?
.get(0)
};
transaction
.execute(
"INSERT INTO song_request_state(owner_user_id,component_instance_id) \
VALUES($1,$2) ON CONFLICT(component_instance_id) DO NOTHING",
&[&tenant.user_id, &component_id],
)
.await?;
transaction.commit().await?;
}
Ok(())
}
pub async fn hydrate_tenant(
&self,
owner_id: Uuid,
@@ -72,6 +128,12 @@ impl TenantRepository {
kind: &str,
name: &str,
) -> Result<ComponentInstance, RepositoryError> {
// 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 {
return Err(RepositoryError::Forbidden);
}
let runtime = self
.registry
.runtime(kind)
@@ -123,6 +185,17 @@ impl TenantRepository {
let mut client = self.db.get().await?;
let transaction = client.transaction().await?;
Db::set_tenant(&transaction, owner_id).await?;
let kind: String = transaction
.query_opt(
"SELECT kind FROM component_instances WHERE owner_user_id=$1 AND id=$2",
&[&owner_id, &component_id],
)
.await?
.ok_or(RepositoryError::NotFound)?
.get(0);
if kind == SONG_REQUEST_KIND {
return Err(RepositoryError::Forbidden);
}
let changed = transaction
.execute(
"DELETE FROM component_instances WHERE owner_user_id=$1 AND id=$2",