feat(modules): group-docs schema + kind-generalized registry (§11.6 docs C1)
Data layer for extending shared group collections from KV to docs. KV behavior
unchanged (callers pass kind="kv").
- 0054_group_docs.sql: widen the group_collections kind CHECK to ('kv','docs')
and add the group-keyed group_docs store (clone of docs/0013, keyed by
group_id, CASCADE on group delete) + its (group_id,collection) and data GIN
indexes.
- group_collection_repo: thread a `kind` parameter through list_for_owner,
resolve_owning_group, insert/delete_collection_tx; add list_all_for_owner ->
(name,kind) for the kind-aware diff (D4). Relocate the injectable
GroupCollectionResolver trait + Postgres impl here (now shared by kv+docs;
resolve takes kind) — was in group_kv_service.
- group_docs_repo: a near-clone of docs_repo keyed by group_id; find reuses the
generalized build_find_query.
- docs_repo: generalize build_find_query(table, owner_col, owner_id, …) — both
are compile-time literals (no injection); app docs passes ("docs","app_id"),
group docs ("group_docs","group_id"). row_to_doc/build_find_query are now
pub(crate) for reuse.
Schema snapshot re-blessed (55 migrations).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,48 +22,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use picloud_shared::{AppId, GroupId, GroupKvError, GroupKvService, KvListPage, SdkCallCx};
|
||||
use sqlx::PgPool;
|
||||
use picloud_shared::{GroupId, GroupKvError, GroupKvService, KvListPage, SdkCallCx};
|
||||
|
||||
use crate::authz::{self, AuthzRepo, Capability};
|
||||
use crate::group_collection_repo;
|
||||
use crate::group_collection_repo::GroupCollectionResolver;
|
||||
use crate::group_kv_repo::{GroupKvRepo, GroupKvRepoError};
|
||||
use crate::kv_service::kv_max_value_bytes_from_env;
|
||||
|
||||
/// Resolves a shared-collection name to its owning group for a calling app
|
||||
/// (nearest ancestor group on the app's chain that declares it). Behind a
|
||||
/// trait so unit tests can inject a fake without Postgres.
|
||||
#[async_trait]
|
||||
pub trait GroupCollectionResolver: Send + Sync {
|
||||
async fn resolve_owning_group(
|
||||
&self,
|
||||
app_id: AppId,
|
||||
name: &str,
|
||||
) -> Result<Option<GroupId>, sqlx::Error>;
|
||||
}
|
||||
|
||||
/// Postgres-backed resolver — delegates to [`group_collection_repo`].
|
||||
pub struct PostgresGroupCollectionResolver {
|
||||
pool: PgPool,
|
||||
}
|
||||
|
||||
impl PostgresGroupCollectionResolver {
|
||||
#[must_use]
|
||||
pub fn new(pool: PgPool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GroupCollectionResolver for PostgresGroupCollectionResolver {
|
||||
async fn resolve_owning_group(
|
||||
&self,
|
||||
app_id: AppId,
|
||||
name: &str,
|
||||
) -> Result<Option<GroupId>, sqlx::Error> {
|
||||
group_collection_repo::resolve_owning_group(&self.pool, app_id, name).await
|
||||
}
|
||||
}
|
||||
/// The registry `kind` this service resolves.
|
||||
const KIND_KV: &str = "kv";
|
||||
|
||||
pub struct GroupKvServiceImpl {
|
||||
repo: Arc<dyn GroupKvRepo>,
|
||||
@@ -109,7 +76,7 @@ impl GroupKvServiceImpl {
|
||||
return Err(GroupKvError::InvalidCollection);
|
||||
}
|
||||
self.resolver
|
||||
.resolve_owning_group(cx.app_id, collection)
|
||||
.resolve_owning_group(cx.app_id, collection, KIND_KV)
|
||||
.await
|
||||
.map_err(|e| GroupKvError::Backend(e.to_string()))?
|
||||
.ok_or_else(|| GroupKvError::CollectionNotShared(collection.to_string()))
|
||||
@@ -220,7 +187,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::authz::{AuthzError, AuthzRepo};
|
||||
use picloud_shared::{
|
||||
AdminUserId, AppRole, ExecutionId, InstanceRole, Principal, RequestId, ScriptId, UserId,
|
||||
AdminUserId, AppId, AppRole, ExecutionId, InstanceRole, Principal, RequestId, ScriptId,
|
||||
UserId,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use tokio::sync::Mutex;
|
||||
@@ -321,6 +289,7 @@ mod tests {
|
||||
&self,
|
||||
app_id: AppId,
|
||||
name: &str,
|
||||
_kind: &str,
|
||||
) -> Result<Option<GroupId>, sqlx::Error> {
|
||||
Ok(self.map.get(&(app_id, name.to_lowercase())).copied())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user