Files
server/core/lib/Stores/UserAccountsStore.php
2026-02-10 18:46:11 -05:00

297 lines
9.4 KiB
PHP

<?php
namespace KTXC\Stores;
use KTXC\Db\DataStore;
use KTXF\Utile\UUID;
class UserAccountsStore
{
public function __construct(protected DataStore $store)
{ }
// =========================================================================
// User Operations (Full User Object)
// =========================================================================
/**
* List all users for a tenant with optional filters
*/
public function listUsers(string $tenant, array $filters = []): array
{
// Build filter
$filter = ['tid' => $tenant];
if (isset($filters['enabled'])) {
$filter['enabled'] = (bool)$filters['enabled'];
}
if (isset($filters['role'])) {
$filter['roles'] = $filters['role'];
}
// Fetch users with aggregated role data
$pipeline = [
['$match' => $filter],
[
'$lookup' => [
'from' => 'user_roles',
'localField' => 'roles',
'foreignField' => 'rid',
'as' => 'role_details'
]
],
[
'$addFields' => [
'permissions' => [
'$reduce' => [
'input' => [
'$map' => [
'input' => '$role_details',
'as' => 'r',
'in' => ['$ifNull' => ['$$r.permissions', []]]
]
],
'initialValue' => [],
'in' => ['$setUnion' => ['$$value', '$$this']]
]
]
]
],
['$unset' => 'role_details'],
['$sort' => ['label' => 1]]
];
$cursor = $this->store->selectCollection('user_accounts')->aggregate($pipeline);
$users = [];
foreach ($cursor as $entry) {
$users[] = (array)$entry;
}
return $users;
}
public function fetchByIdentity(string $tenant, string $identity): array | null
{
$pipeline = [
[
'$match' => [
'tid' => $tenant,
'identity' => $identity
]
],
[
'$lookup' => [
'from' => 'user_roles',
'localField' => 'roles', // Array field in `users`
'foreignField' => 'rid', // Scalar field in `user_roles`
'as' => 'role_details'
]
],
// Add flattened, deduplicated permissions while preserving all original user fields
[
'$addFields' => [
'permissions' => [
'$reduce' => [
'input' => [
'$map' => [
'input' => '$role_details',
'as' => 'r',
'in' => [ '$ifNull' => ['$$r.permissions', []] ]
]
],
'initialValue' => [],
'in' => [ '$setUnion' => ['$$value', '$$this'] ]
]
]
]
],
// Optionally remove expanded role documents from output
[ '$unset' => 'role_details' ]
];
$entry = $this->store->selectCollection('user_accounts')->aggregate($pipeline)->toArray()[0] ?? null;
if (!$entry) { return null; }
return (array)$entry;
}
public function fetchByIdentifier(string $tenant, string $identifier): array | null
{
$pipeline = [
[
'$match' => [
'tid' => $tenant,
'uid' => $identifier
]
],
[
'$lookup' => [
'from' => 'user_roles',
'localField' => 'roles',
'foreignField' => 'rid',
'as' => 'role_details'
]
],
[
'$addFields' => [
'permissions' => [
'$reduce' => [
'input' => [
'$map' => [
'input' => '$role_details',
'as' => 'r',
'in' => [ '$ifNull' => ['$$r.permissions', []] ]
]
],
'initialValue' => [],
'in' => [ '$setUnion' => ['$$value', '$$this'] ]
]
]
]
],
[ '$unset' => 'role_details' ]
];
$entry = $this->store->selectCollection('user_accounts')->aggregate($pipeline)->toArray()[0] ?? null;
if (!$entry) { return null; }
return (array)$entry;
}
public function fetchByProviderSubject(string $tenant, string $provider, string $subject): array | null
{
$entry = $this->store->selectCollection('user_accounts')->findOne([
'tid' => $tenant,
'provider' => $provider,
'provider_subject' => $subject
]);
if (!$entry) { return null; }
return (array)$entry;
}
public function createUser(string $tenant, array $userData): array
{
$userData['tid'] = $tenant;
$userData['uid'] = $userData['uid'] ?? UUID::v4();
$userData['enabled'] = $userData['enabled'] ?? true;
$userData['roles'] = $userData['roles'] ?? [];
$userData['profile'] = $userData['profile'] ?? [];
$userData['settings'] = $userData['settings'] ?? [];
$this->store->selectCollection('user_accounts')->insertOne($userData);
return $this->fetchByIdentifier($tenant, $userData['uid']);
}
public function updateUser(string $tenant, string $uid, array $updates): bool
{
$result = $this->store->selectCollection('user_accounts')->updateOne(
['tid' => $tenant, 'uid' => $uid],
['$set' => $updates]
);
return $result->getModifiedCount() > 0;
}
public function deleteUser(string $tenant, string $uid): bool
{
$result = $this->store->selectCollection('user_accounts')->deleteOne([
'tid' => $tenant,
'uid' => $uid
]);
return $result->getDeletedCount() > 0;
}
// =========================================================================
// Profile Operations
// =========================================================================
public function fetchProfile(string $tenant, string $uid): ?array
{
$user = $this->store->selectCollection('user_accounts')->findOne(
['tid' => $tenant, 'uid' => $uid],
['projection' => ['profile' => 1, 'provider_managed_fields' => 1]]
);
if (!$user) {
return null;
}
return [
'profile' => $user['profile'] ?? [],
'provider_managed_fields' => $user['provider_managed_fields'] ?? [],
];
}
public function storeProfile(string $tenant, string $uid, array $profileFields): bool
{
if (empty($profileFields)) {
return false;
}
$updates = [];
foreach ($profileFields as $key => $value) {
$updates["profile.{$key}"] = $value;
}
$result = $this->store->selectCollection('user_accounts')->updateOne(
['tid' => $tenant, 'uid' => $uid],
['$set' => $updates]
);
return $result->getModifiedCount() > 0;
}
// =========================================================================
// Settings Operations
// =========================================================================
public function fetchSettings(string $tenant, string $uid, array $settings = []): ?array
{
// Only fetch the settings field from the database
$user = $this->store->selectCollection('user_accounts')->findOne(
['tid' => $tenant, 'uid' => $uid],
['projection' => ['settings' => 1]]
);
if (!$user) {
return null;
}
$userSettings = $user['settings'] ?? [];
if (empty($settings)) {
return $userSettings;
}
$result = [];
foreach ($settings as $key) {
$result[$key] = $userSettings[$key] ?? null;
}
return $result;
}
public function storeSettings(string $tenant, string $uid, array $settings): bool
{
if (empty($settings)) {
return false;
}
$updates = [];
foreach ($settings as $key => $value) {
$updates["settings.{$key}"] = $value;
}
$result = $this->store->selectCollection('user_accounts')->updateOne(
['tid' => $tenant, 'uid' => $uid],
['$set' => $updates]
);
// Return true if document was matched (exists), even if not modified
return $result->getMatchedCount() > 0;
}
}