mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
refactor(account-groups): remove unused members endpoint
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use gateway_core::routing::{AccountGroupId, ProviderKind};
|
||||
use gateway_core::routing::AccountGroupId;
|
||||
|
||||
use super::{PageSize, Revision, observability::DecimalAmount};
|
||||
|
||||
@@ -94,25 +94,6 @@ pub struct AccountGroupPage {
|
||||
pub page_size: u16,
|
||||
}
|
||||
|
||||
/// Lightweight account shown by the membership editor.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupMember {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub provider_kind: ProviderKind,
|
||||
pub email: Option<String>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// Complete membership projection for one group.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountGroupMembers {
|
||||
pub config_revision: Revision,
|
||||
pub id: AccountGroupId,
|
||||
pub items: Vec<AccountGroupMember>,
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
/// Create an account group.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CreateAccountGroup {
|
||||
|
||||
@@ -11,8 +11,8 @@ use super::backup::BackupStorePorts;
|
||||
use crate::model::{
|
||||
MutationContext, Revision,
|
||||
account_groups::{
|
||||
AccountGroupListQuery, AccountGroupMembers, AccountGroupMutation, AccountGroupPage,
|
||||
DeleteAccountGroup, NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
AccountGroupListQuery, AccountGroupMutation, AccountGroupPage, DeleteAccountGroup,
|
||||
NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
},
|
||||
accounts::{
|
||||
AccountListQuery, AccountPage, AccountPageItem, AccountUpdateResult, AccountUsage,
|
||||
@@ -226,7 +226,7 @@ pub trait ClientKeyStore: Send + Sync {
|
||||
) -> AdminStoreResult<Revision>;
|
||||
}
|
||||
|
||||
/// Provider-neutral account group management and membership transactions.
|
||||
/// Provider-neutral account group management transactions.
|
||||
#[async_trait]
|
||||
pub trait AccountGroupStore: Send + Sync {
|
||||
async fn list_account_groups(
|
||||
@@ -234,11 +234,6 @@ pub trait AccountGroupStore: Send + Sync {
|
||||
query: AccountGroupListQuery,
|
||||
) -> AdminStoreResult<AccountGroupPage>;
|
||||
|
||||
async fn account_group_members(
|
||||
&self,
|
||||
id: &gateway_core::routing::AccountGroupId,
|
||||
) -> AdminStoreResult<AccountGroupMembers>;
|
||||
|
||||
async fn create_account_group(
|
||||
&self,
|
||||
command: NewAccountGroup,
|
||||
|
||||
@@ -10,9 +10,8 @@ use crate::{
|
||||
model::{
|
||||
AdminError, MutationContext,
|
||||
account_groups::{
|
||||
AccountGroupListQuery, AccountGroupMembers, AccountGroupMutation, AccountGroupPage,
|
||||
CreateAccountGroup, DeleteAccountGroup, NewAccountGroup, SetAccountGroupEnabled,
|
||||
UpdateAccountGroup,
|
||||
AccountGroupListQuery, AccountGroupMutation, AccountGroupPage, CreateAccountGroup,
|
||||
DeleteAccountGroup, NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
},
|
||||
},
|
||||
ports::store::AccountGroupStore,
|
||||
@@ -24,7 +23,6 @@ use super::{map_store_error, publish_committed};
|
||||
#[async_trait]
|
||||
pub trait AccountGroupService: Send + Sync {
|
||||
async fn list(&self, query: AccountGroupListQuery) -> Result<AccountGroupPage, AdminError>;
|
||||
async fn members(&self, id: &AccountGroupId) -> Result<AccountGroupMembers, AdminError>;
|
||||
async fn create(
|
||||
&self,
|
||||
context: &MutationContext,
|
||||
@@ -80,13 +78,6 @@ impl AccountGroupService for DefaultAccountGroupService {
|
||||
.map_err(|error| map_store_error(error, "account group"))
|
||||
}
|
||||
|
||||
async fn members(&self, id: &AccountGroupId) -> Result<AccountGroupMembers, AdminError> {
|
||||
self.store
|
||||
.account_group_members(id)
|
||||
.await
|
||||
.map_err(|error| map_store_error(error, "account group"))
|
||||
}
|
||||
|
||||
async fn create(
|
||||
&self,
|
||||
context: &MutationContext,
|
||||
|
||||
@@ -21,8 +21,8 @@ use gateway_admin::{
|
||||
model::{
|
||||
MutationContext, Revision,
|
||||
account_groups::{
|
||||
AccountGroupListQuery, AccountGroupMembers, AccountGroupMutation, AccountGroupPage,
|
||||
DeleteAccountGroup, NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
AccountGroupListQuery, AccountGroupMutation, AccountGroupPage, DeleteAccountGroup,
|
||||
NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
},
|
||||
accounts::{
|
||||
AccountListQuery, AccountPage, AccountUpdateResult, AccountUsage,
|
||||
@@ -252,13 +252,6 @@ impl AccountGroupStore for UnavailableAccountGroupStore {
|
||||
Err(unavailable("account groups"))
|
||||
}
|
||||
|
||||
async fn account_group_members(
|
||||
&self,
|
||||
_: &gateway_core::routing::AccountGroupId,
|
||||
) -> AdminStoreResult<AccountGroupMembers> {
|
||||
Err(unavailable("account group members"))
|
||||
}
|
||||
|
||||
async fn create_account_group(
|
||||
&self,
|
||||
_: NewAccountGroup,
|
||||
|
||||
@@ -14,9 +14,8 @@ use gateway_admin::model::{
|
||||
PageSize,
|
||||
account_groups::{
|
||||
AccountGroupAccountSummary, AccountGroupCapacity, AccountGroupColor, AccountGroupListQuery,
|
||||
AccountGroupMember, AccountGroupMembers, AccountGroupMutation, AccountGroupPage,
|
||||
AccountGroupRecord, AccountGroupUsage, CreateAccountGroup, DeleteAccountGroup,
|
||||
SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
AccountGroupMutation, AccountGroupPage, AccountGroupRecord, AccountGroupUsage,
|
||||
CreateAccountGroup, DeleteAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
},
|
||||
};
|
||||
use gateway_core::routing::AccountGroupId;
|
||||
@@ -91,12 +90,6 @@ struct AccountGroupIdRequest {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct AccountGroupIdQuery {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AccountGroupView {
|
||||
@@ -207,48 +200,6 @@ impl From<AccountGroupPage> for AccountGroupPageData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AccountGroupMemberView {
|
||||
id: String,
|
||||
name: String,
|
||||
provider_kind: String,
|
||||
email: Option<String>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl From<AccountGroupMember> for AccountGroupMemberView {
|
||||
fn from(member: AccountGroupMember) -> Self {
|
||||
Self {
|
||||
id: member.id,
|
||||
name: member.name,
|
||||
provider_kind: member.provider_kind.to_string(),
|
||||
email: member.email,
|
||||
enabled: member.enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AccountGroupMembersData {
|
||||
id: String,
|
||||
items: Vec<AccountGroupMemberView>,
|
||||
total: u64,
|
||||
config_revision: u64,
|
||||
}
|
||||
|
||||
impl From<AccountGroupMembers> for AccountGroupMembersData {
|
||||
fn from(members: AccountGroupMembers) -> Self {
|
||||
Self {
|
||||
id: members.id.to_string(),
|
||||
items: members.items.into_iter().map(Into::into).collect(),
|
||||
total: members.total,
|
||||
config_revision: members.config_revision.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AccountGroupMutationData {
|
||||
@@ -279,7 +230,6 @@ where
|
||||
.route("/api/admin/account-groups/enable", post(enable::<S>))
|
||||
.route("/api/admin/account-groups/disable", post(disable::<S>))
|
||||
.route("/api/admin/account-groups/delete", post(delete::<S>))
|
||||
.route("/api/admin/account-groups/members", get(members::<S>))
|
||||
}
|
||||
|
||||
async fn list<S>(
|
||||
@@ -302,27 +252,6 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
async fn members<S>(
|
||||
_auth: AdminAuth,
|
||||
State(state): State<S>,
|
||||
Query(query): Query<AccountGroupIdQuery>,
|
||||
) -> Result<impl IntoResponse, AdminError>
|
||||
where
|
||||
S: AdminSessionState + Send + Sync,
|
||||
{
|
||||
let id = group_id(query.id)?;
|
||||
let result = state
|
||||
.admin_services()
|
||||
.account_groups()
|
||||
.members(&id)
|
||||
.await
|
||||
.map_err(map_service_error)?;
|
||||
Ok(AdminResponse::new(
|
||||
StatusCode::OK,
|
||||
AdminEnvelope::ok(AccountGroupMembersData::from(result)),
|
||||
))
|
||||
}
|
||||
|
||||
async fn create<S>(
|
||||
auth: AdminAuth,
|
||||
State(state): State<S>,
|
||||
|
||||
@@ -44,27 +44,6 @@ async fn list_route_should_keep_camel_case_group_and_page_wire() {
|
||||
assert!(value["data"]["items"][0].get("member_count").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn members_route_should_keep_provider_kind_and_member_wire() {
|
||||
let fixture = authenticated_fixture().await;
|
||||
let response = request(
|
||||
router(&fixture),
|
||||
Method::GET,
|
||||
&format!("/api/admin/account-groups/members?id={PRIMARY_GROUP_ID}"),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let value = response_json(response).await;
|
||||
assert_eq!(value["data"]["id"], PRIMARY_GROUP_ID);
|
||||
assert_eq!(value["data"]["total"], 2);
|
||||
assert_eq!(value["data"]["items"][0]["providerKind"], "openai");
|
||||
assert_eq!(value["data"]["items"][0]["email"], "openai@example.invalid");
|
||||
assert!(value["data"]["items"][0].get("provider_kind").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_route_should_create_an_empty_group() {
|
||||
let fixture = authenticated_fixture().await;
|
||||
|
||||
@@ -15,9 +15,9 @@ use gateway_admin::{
|
||||
MutationContext, Revision,
|
||||
account_groups::{
|
||||
AccountGroupAccountSummary, AccountGroupCapacity, AccountGroupColor,
|
||||
AccountGroupListQuery, AccountGroupMember, AccountGroupMembers, AccountGroupMutation,
|
||||
AccountGroupPage, AccountGroupRecord, AccountGroupUsage, DeleteAccountGroup,
|
||||
NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
AccountGroupListQuery, AccountGroupMutation, AccountGroupPage, AccountGroupRecord,
|
||||
AccountGroupUsage, DeleteAccountGroup, NewAccountGroup, SetAccountGroupEnabled,
|
||||
UpdateAccountGroup,
|
||||
},
|
||||
accounts::{
|
||||
AccountListQuery, AccountPage, AccountPageItem, AccountUpdateResult, AccountUsage,
|
||||
@@ -361,7 +361,6 @@ pub(super) const SECONDARY_GROUP_ID: &str = "grp_2222222222222222222222222222222
|
||||
struct MemoryAccountGroupState {
|
||||
revision: Revision,
|
||||
groups: BTreeMap<gateway_core::routing::AccountGroupId, AccountGroupRecord>,
|
||||
members: BTreeMap<gateway_core::routing::AccountGroupId, Vec<AccountGroupMember>>,
|
||||
}
|
||||
|
||||
pub(super) struct MemoryAccountGroupStore {
|
||||
@@ -373,25 +372,11 @@ impl MemoryAccountGroupStore {
|
||||
let now = Utc::now();
|
||||
let primary_id = group_id(PRIMARY_GROUP_ID);
|
||||
let secondary_id = group_id(SECONDARY_GROUP_ID);
|
||||
let openai = AccountGroupMember {
|
||||
id: "acct_openai".to_owned(),
|
||||
name: "OpenAI primary".to_owned(),
|
||||
provider_kind: ProviderKind::new("openai").expect("provider kind"),
|
||||
email: Some("openai@example.invalid".to_owned()),
|
||||
enabled: true,
|
||||
};
|
||||
let xai = AccountGroupMember {
|
||||
id: "acct_xai".to_owned(),
|
||||
name: "xAI primary".to_owned(),
|
||||
provider_kind: ProviderKind::new("xai").expect("provider kind"),
|
||||
email: None,
|
||||
enabled: false,
|
||||
};
|
||||
let groups = BTreeMap::from([
|
||||
(
|
||||
primary_id.clone(),
|
||||
AccountGroupRecord {
|
||||
id: primary_id.clone(),
|
||||
id: primary_id,
|
||||
name: "Alpha routing".to_owned(),
|
||||
description: Some("Primary traffic".to_owned()),
|
||||
color: group_color("#2563ebff"),
|
||||
@@ -432,7 +417,6 @@ impl MemoryAccountGroupStore {
|
||||
state: Mutex::new(MemoryAccountGroupState {
|
||||
revision: Revision::new(7).expect("revision"),
|
||||
groups,
|
||||
members: BTreeMap::from([(primary_id, vec![openai, xai])]),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -481,23 +465,6 @@ impl AccountGroupStore for MemoryAccountGroupStore {
|
||||
})
|
||||
}
|
||||
|
||||
async fn account_group_members(
|
||||
&self,
|
||||
id: &gateway_core::routing::AccountGroupId,
|
||||
) -> AdminStoreResult<AccountGroupMembers> {
|
||||
let state = self.state.lock().expect("account groups");
|
||||
if !state.groups.contains_key(id) {
|
||||
return Err(not_found("account group"));
|
||||
}
|
||||
let items = state.members.get(id).cloned().unwrap_or_default();
|
||||
Ok(AccountGroupMembers {
|
||||
config_revision: state.revision,
|
||||
id: id.clone(),
|
||||
total: items.len() as u64,
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_account_group(
|
||||
&self,
|
||||
command: NewAccountGroup,
|
||||
@@ -521,8 +488,6 @@ impl AccountGroupStore for MemoryAccountGroupStore {
|
||||
updated_at: now,
|
||||
};
|
||||
state.groups.insert(command.id.clone(), record);
|
||||
state.members.insert(command.id.clone(), Vec::new());
|
||||
refresh_group_counts(&mut state, &command.id);
|
||||
mutation(&mut state, command.id, true)
|
||||
}
|
||||
|
||||
@@ -567,7 +532,6 @@ impl AccountGroupStore for MemoryAccountGroupStore {
|
||||
if state.groups.remove(&command.id).is_none() {
|
||||
return Err(not_found("account group"));
|
||||
}
|
||||
state.members.remove(&command.id);
|
||||
mutation(&mut state, command.id, false)
|
||||
}
|
||||
}
|
||||
@@ -576,24 +540,6 @@ fn group_id(value: &str) -> gateway_core::routing::AccountGroupId {
|
||||
gateway_core::routing::AccountGroupId::new(value).expect("account group ID")
|
||||
}
|
||||
|
||||
fn refresh_group_counts(
|
||||
state: &mut MemoryAccountGroupState,
|
||||
id: &gateway_core::routing::AccountGroupId,
|
||||
) {
|
||||
let members = state.members.get(id).map(Vec::as_slice).unwrap_or_default();
|
||||
let provider_counts = members.iter().fold(BTreeMap::new(), |mut counts, member| {
|
||||
*counts
|
||||
.entry(member.provider_kind.as_str().to_owned())
|
||||
.or_insert(0) += 1;
|
||||
counts
|
||||
});
|
||||
if let Some(record) = state.groups.get_mut(id) {
|
||||
record.member_count = members.len() as u64;
|
||||
record.provider_counts = provider_counts;
|
||||
record.updated_at = Utc::now();
|
||||
}
|
||||
}
|
||||
|
||||
fn mutation(
|
||||
state: &mut MemoryAccountGroupState,
|
||||
id: gateway_core::routing::AccountGroupId,
|
||||
|
||||
@@ -13,18 +13,17 @@ use gateway_admin::{
|
||||
MutationContext,
|
||||
account_groups::{
|
||||
AccountGroupAccountSummary, AccountGroupCapacity, AccountGroupColor,
|
||||
AccountGroupListQuery, AccountGroupMember, AccountGroupMembers, AccountGroupMutation,
|
||||
AccountGroupPage, AccountGroupRecord, AccountGroupUsage, DeleteAccountGroup,
|
||||
NewAccountGroup, SetAccountGroupEnabled, UpdateAccountGroup,
|
||||
AccountGroupListQuery, AccountGroupMutation, AccountGroupPage, AccountGroupRecord,
|
||||
AccountGroupUsage, DeleteAccountGroup, NewAccountGroup, SetAccountGroupEnabled,
|
||||
UpdateAccountGroup,
|
||||
},
|
||||
observability::DecimalAmount,
|
||||
},
|
||||
ports::store::{AccountGroupStore, AdminStoreError, AdminStoreResult},
|
||||
};
|
||||
use gateway_core::{
|
||||
engine::credential::AccountStatus,
|
||||
provider_ports::ProviderCooldownPort,
|
||||
routing::{AccountGroupId, ProviderKind},
|
||||
engine::credential::AccountStatus, provider_ports::ProviderCooldownPort,
|
||||
routing::AccountGroupId,
|
||||
};
|
||||
use sqlx::{PgPool, Postgres, QueryBuilder, Row as _, Transaction};
|
||||
|
||||
@@ -178,36 +177,6 @@ impl AccountGroupStore for PgAccountGroupRepository {
|
||||
})
|
||||
}
|
||||
|
||||
async fn account_group_members(
|
||||
&self,
|
||||
id: &AccountGroupId,
|
||||
) -> AdminStoreResult<AccountGroupMembers> {
|
||||
self.required_record(id).await?;
|
||||
let rows = sqlx::query(
|
||||
"select a.id, a.name, a.provider_kind, a.email, a.enabled
|
||||
from account_group_accounts m
|
||||
join provider_accounts a on a.id = m.provider_account_id
|
||||
where m.account_group_id = $1
|
||||
order by a.provider_kind, lower(a.name), a.id",
|
||||
)
|
||||
.bind(id.as_str())
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|_| admin_store_error(ENTITY, unavailable("load account group members")))?;
|
||||
let items = rows
|
||||
.iter()
|
||||
.map(group_member)
|
||||
.collect::<StoreResult<Vec<_>>>()
|
||||
.map_err(|error| admin_store_error(ENTITY, error))?;
|
||||
Ok(AccountGroupMembers {
|
||||
config_revision: self.current_revision().await?,
|
||||
id: id.clone(),
|
||||
total: u64::try_from(items.len())
|
||||
.map_err(|_| invalid_admin("member count overflow"))?,
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_account_group(
|
||||
&self,
|
||||
command: NewAccountGroup,
|
||||
@@ -674,28 +643,6 @@ async fn group_costs(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn group_member(row: &sqlx::postgres::PgRow) -> StoreResult<AccountGroupMember> {
|
||||
Ok(AccountGroupMember {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_| invalid("invalid member id"))?,
|
||||
name: row
|
||||
.try_get("name")
|
||||
.map_err(|_| invalid("invalid member name"))?,
|
||||
provider_kind: ProviderKind::new(
|
||||
row.try_get::<String, _>("provider_kind")
|
||||
.map_err(|_| invalid("invalid member provider"))?,
|
||||
)
|
||||
.map_err(|_| invalid("invalid member provider"))?,
|
||||
email: row
|
||||
.try_get("email")
|
||||
.map_err(|_| invalid("invalid member email"))?,
|
||||
enabled: row
|
||||
.try_get("enabled")
|
||||
.map_err(|_| invalid("invalid member enabled"))?,
|
||||
})
|
||||
}
|
||||
|
||||
fn count_value(row: &sqlx::postgres::PgRow, field: &str) -> StoreResult<u64> {
|
||||
u64::try_from(
|
||||
row.try_get::<i64, _>(field)
|
||||
|
||||
@@ -122,20 +122,6 @@ async fn groups_aggregate_cross_provider_members_and_key_bindings_without_multip
|
||||
assert!(empty.provider_counts.is_empty());
|
||||
assert_eq!(empty.client_key_count, 1);
|
||||
|
||||
let members = groups
|
||||
.account_group_members(&mixed_group)
|
||||
.await
|
||||
.expect("load mixed group members");
|
||||
assert_eq!(members.total, 2);
|
||||
assert_eq!(
|
||||
members
|
||||
.items
|
||||
.iter()
|
||||
.map(|member| member.provider_kind.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["openai", "xai"]
|
||||
);
|
||||
|
||||
let all_key = keys
|
||||
.reveal_client_key(&client_key_id("key_all_accounts"))
|
||||
.await
|
||||
|
||||
@@ -222,7 +222,6 @@ OAuth start 使用:
|
||||
| `POST` | `/api/admin/account-groups/enable` | `{ id }` | 启用 |
|
||||
| `POST` | `/api/admin/account-groups/disable` | `{ id }` | 禁用;已绑定 Key 保持受限,不回退到全部账号 |
|
||||
| `POST` | `/api/admin/account-groups/delete` | `{ id }` | 删除未被 Client Key 引用的组 |
|
||||
| `GET` | `/api/admin/account-groups/members` | `id` | 查询跨 Provider 成员 |
|
||||
|
||||
列表数据为 `{ items, page, configRevision }`,其中 item 返回 `memberCount`、按 Provider 聚合的
|
||||
`providerCounts` 和 `clientKeyCount`。成员响应为 `{ id, items, total, configRevision }`;成员的
|
||||
|
||||
@@ -48,21 +48,6 @@ export interface AccountGroupListResponse {
|
||||
configRevision: number
|
||||
}
|
||||
|
||||
export interface AccountGroupMember {
|
||||
id: string
|
||||
name: string
|
||||
providerKind: string
|
||||
email: string | null
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface AccountGroupMembersResponse {
|
||||
id: string
|
||||
items: AccountGroupMember[]
|
||||
total: number
|
||||
configRevision: number
|
||||
}
|
||||
|
||||
export interface AccountGroupMutationResponse {
|
||||
id: string
|
||||
record: AccountGroup | null
|
||||
@@ -140,11 +125,3 @@ export function deleteAccountGroup(data: AccountGroupIdParam) {
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function getAccountGroupMembers(data: AccountGroupIdParam) {
|
||||
return request<AccountGroupMembersResponse>({
|
||||
url: '/api/admin/account-groups/members',
|
||||
method: 'GET',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import type { AccountGroup, AccountGroupMember } from '@/api'
|
||||
import type { AccountGroup } from '@/api'
|
||||
|
||||
import { onMounted, shallowRef } from 'vue'
|
||||
import { getAccountGroupMembers, getAccountGroups } from '@/api'
|
||||
import { getAccountGroups } from '@/api'
|
||||
import { toast } from '@/components/base/BaseToast'
|
||||
import { errorMessage } from '@/utils/async'
|
||||
|
||||
export function useAccountGroupCatalog(options: { immediate?: boolean } = {}) {
|
||||
const groups = shallowRef<AccountGroup[]>([])
|
||||
const membersByGroupId = shallowRef<Record<string, AccountGroupMember[]>>({})
|
||||
const loading = shallowRef(false)
|
||||
const loadingMemberIds = shallowRef<ReadonlySet<string>>(new Set())
|
||||
const memberRequests = new Map<string, Promise<AccountGroupMember[]>>()
|
||||
|
||||
async function loadGroups() {
|
||||
loading.value = true
|
||||
@@ -33,51 +30,6 @@ export function useAccountGroupCatalog(options: { immediate?: boolean } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMembers(groupId: string, force = false) {
|
||||
if (!force && Object.hasOwn(membersByGroupId.value, groupId))
|
||||
return membersByGroupId.value[groupId] ?? []
|
||||
|
||||
const existing = memberRequests.get(groupId)
|
||||
if (existing)
|
||||
return existing
|
||||
|
||||
const request = (async () => {
|
||||
setMemberLoading(groupId, true)
|
||||
try {
|
||||
const result = await getAccountGroupMembers({ id: groupId })
|
||||
membersByGroupId.value = {
|
||||
...membersByGroupId.value,
|
||||
[groupId]: result.items,
|
||||
}
|
||||
return result.items
|
||||
}
|
||||
finally {
|
||||
setMemberLoading(groupId, false)
|
||||
memberRequests.delete(groupId)
|
||||
}
|
||||
})()
|
||||
memberRequests.set(groupId, request)
|
||||
return request
|
||||
}
|
||||
|
||||
async function ensureMembers(groupIds: string[]) {
|
||||
try {
|
||||
await Promise.all([...new Set(groupIds)].map(id => loadMembers(id)))
|
||||
}
|
||||
catch (error: unknown) {
|
||||
toast.error(errorMessage(error, '分组成员加载失败'))
|
||||
}
|
||||
}
|
||||
|
||||
function setMemberLoading(groupId: string, value: boolean) {
|
||||
const next = new Set(loadingMemberIds.value)
|
||||
if (value)
|
||||
next.add(groupId)
|
||||
else
|
||||
next.delete(groupId)
|
||||
loadingMemberIds.value = next
|
||||
}
|
||||
|
||||
if (options.immediate !== false) {
|
||||
onMounted(() => {
|
||||
void loadGroups()
|
||||
@@ -86,11 +38,7 @@ export function useAccountGroupCatalog(options: { immediate?: boolean } = {}) {
|
||||
|
||||
return {
|
||||
groups,
|
||||
membersByGroupId,
|
||||
loading,
|
||||
loadingMemberIds,
|
||||
loadGroups,
|
||||
loadMembers,
|
||||
ensureMembers,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user