Files
server/core/lib/Stores/UserStore.php
2025-12-21 10:09:54 -05:00

257 lines
8.1 KiB
PHP

<?php
namespace KTXC\Stores;
use KTXC\Db\DataStore;
use KTXC\Db\Collection;
class UserStore
{
public function __construct(protected DataStore $store)
{ }
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('users')->aggregate($pipeline)->toArray()[0] ?? null;
if (!$entry) { return null; }
return (array)$entry;
}
public function fetchByIdentifier(string $tenant, string $identifier): array | null
{
$entry = $this->store->selectCollection('users')->findOne(['tid' => $tenant, 'uid' => $identifier]);
if (!$entry) { return null; }
return (array)$entry;
}
/**
* Fetch user settings from the embedded settings field in the user document
*
* @param string $tenant Tenant identifier
* @param string $identifier User identifier
* @param array $settings Optional array of specific setting keys to retrieve
* @return array|null Settings array or null if user not found
*/
public function fetchSettings(string $tenant, string $identifier, array $keys = []): array | null
{
$entry = $this->store->selectCollection('users')->findOne(
['tid' => $tenant, 'uid' => $identifier],
['projection' => ['settings' => 1]]
);
if (!$entry) {
return null;
}
$settings = (array)($entry['settings'] ?? []);
if (empty($keys)) {
return $settings;
}
// Filter to only requested keys
return array_filter(
$settings,
fn($key) => in_array($key, $keys),
ARRAY_FILTER_USE_KEY
);
}
/**
* Store/update user settings in the embedded settings field
*
* @param string $tenant Tenant identifier
* @param string $identifier User identifier
* @param array $settings Key-value pairs to set/update
* @return bool Whether the update was acknowledged
*/
public function storeSettings(string $tenant, string $identifier, array $settings): bool
{
// Build dot-notation update for each setting key
$setFields = [];
foreach ($settings as $key => $value) {
$setFields["settings.$key"] = $value;
}
$result = $this->store->selectCollection('users')->updateOne(
['tid' => $tenant, 'uid' => $identifier],
['$set' => $setFields]
);
return $result->isAcknowledged();
}
/**
* Remove specific settings from a user
*
* @param string $tenant Tenant identifier
* @param string $identifier User identifier
* @param array $keys Setting keys to remove
* @return bool Whether the update was acknowledged
*/
public function removeSettings(string $tenant, string $identifier, array $keys): bool
{
$unsetFields = [];
foreach ($keys as $key) {
$unsetFields["settings.$key"] = "";
}
$result = $this->store->selectCollection('users')->updateOne(
['tid' => $tenant, 'uid' => $identifier],
['$unset' => $unsetFields]
);
return $result->isAcknowledged();
}
/**
* Create a new user
*
* @param array $data User data including tid, uid, identity, label, provider, etc.
* @return string|null The created user's UID or null on failure
*/
public function create(array $data): ?string
{
// Ensure required fields
if (empty($data['tid']) || empty($data['uid']) || empty($data['identity'])) {
return null;
}
// Set defaults
$data['enabled'] = $data['enabled'] ?? true;
$data['roles'] = $data['roles'] ?? [];
$data['profile'] = $data['profile'] ?? [];
$data['settings'] = $data['settings'] ?? [];
$data['initial_login'] = $data['initial_login'] ?? time();
$data['recent_login'] = $data['recent_login'] ?? time();
$result = $this->store->selectCollection('users')->insertOne($data);
if ($result->isAcknowledged()) {
return $data['uid'];
}
return null;
}
/**
* Update user profile fields
*
* @param string $tenant Tenant identifier
* @param string $identifier User identifier
* @param array $profile Profile data to update
* @return bool Whether the update was acknowledged
*/
public function updateProfile(string $tenant, string $identifier, array $profile): bool
{
$setFields = [];
foreach ($profile as $key => $value) {
$setFields["profile.$key"] = $value;
}
$result = $this->store->selectCollection('users')->updateOne(
['tid' => $tenant, 'uid' => $identifier],
['$set' => $setFields]
);
return $result->isAcknowledged();
}
/**
* Update user's last login timestamp
*
* @param string $tenant Tenant identifier
* @param string $identifier User identifier
* @return bool Whether the update was acknowledged
*/
public function updateLastLogin(string $tenant, string $identifier): bool
{
$result = $this->store->selectCollection('users')->updateOne(
['tid' => $tenant, 'uid' => $identifier],
['$set' => ['recent_login' => time()]]
);
return $result->isAcknowledged();
}
/**
* Update user's label
*
* @param string $tenant Tenant identifier
* @param string $identifier User identifier
* @param string $label New label
* @return bool Whether the update was acknowledged
*/
public function updateLabel(string $tenant, string $identifier, string $label): bool
{
$result = $this->store->selectCollection('users')->updateOne(
['tid' => $tenant, 'uid' => $identifier],
['$set' => ['label' => $label]]
);
return $result->isAcknowledged();
}
/**
* Find user by external subject (for external identity providers)
*
* @param string $tenant Tenant identifier
* @param string $provider Provider identifier
* @param string $externalSubject External subject identifier
* @return array|null User data or null if not found
*/
public function fetchByExternalSubject(string $tenant, string $provider, string $externalSubject): ?array
{
$entry = $this->store->selectCollection('users')->findOne([
'tid' => $tenant,
'provider' => $provider,
'external_subject' => $externalSubject
]);
if (!$entry) {
return null;
}
return (array)$entry;
}
}