@valentinkolb/cloud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +69 -0
- package/public/logo.svg +1 -0
- package/scripts/build.ts +113 -0
- package/scripts/preload.ts +73 -0
- package/src/_internal/define-app.ts +399 -0
- package/src/_internal/heartbeat.ts +33 -0
- package/src/_internal/registry.ts +100 -0
- package/src/_internal/runtime-context.ts +38 -0
- package/src/api/accounts-entities.ts +134 -0
- package/src/api/admin-lifecycle.ts +210 -0
- package/src/api/auth/schemas.ts +28 -0
- package/src/api/auth.ts +230 -0
- package/src/api/index.ts +66 -0
- package/src/api/me.ts +206 -0
- package/src/api/search/schemas.ts +43 -0
- package/src/api/search.ts +130 -0
- package/src/clients/core.ts +19 -0
- package/src/config/env.ts +23 -0
- package/src/config/index.ts +6 -0
- package/src/config/ssr.ts +58 -0
- package/src/contracts/app.ts +140 -0
- package/src/contracts/index.ts +5 -0
- package/src/contracts/profile.ts +67 -0
- package/src/contracts/registry.ts +50 -0
- package/src/contracts/settings-types.ts +84 -0
- package/src/contracts/shared.ts +258 -0
- package/src/contracts/widgets.ts +121 -0
- package/src/index.ts +6 -0
- package/src/server/api/index.ts +1 -0
- package/src/server/api/respond.ts +55 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/index.ts +62 -0
- package/src/server/middleware/auth.ts +168 -0
- package/src/server/middleware/index.ts +7 -0
- package/src/server/middleware/middleware.ts +47 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +126 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/validator.ts +35 -0
- package/src/server/services/access.ts +294 -0
- package/src/server/services/freeipa/client.ts +100 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +78 -0
- package/src/server/services/freeipa/tls.ts +48 -0
- package/src/server/services/freeipa/util.ts +60 -0
- package/src/server/services/geo.ts +154 -0
- package/src/server/services/index.ts +28 -0
- package/src/server/services/services.ts +13 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +907 -0
- package/src/services/account-lifecycle/scheduler.ts +347 -0
- package/src/services/account-model.ts +21 -0
- package/src/services/accounts/app.ts +966 -0
- package/src/services/accounts/authz.ts +22 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +45 -0
- package/src/services/accounts/entities.ts +529 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +246 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +64 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +491 -0
- package/src/services/accounts/model.ts +135 -0
- package/src/services/accounts/switching.ts +117 -0
- package/src/services/accounts/users.ts +714 -0
- package/src/services/auth-flows/index.ts +6 -0
- package/src/services/auth-flows/ipa.ts +128 -0
- package/src/services/auth-flows/magic-link.ts +119 -0
- package/src/services/freeipa-config.ts +89 -0
- package/src/services/index.ts +46 -0
- package/src/services/ipa/auth.ts +122 -0
- package/src/services/ipa/groups.ts +684 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/profile.ts +90 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/sync.ts +740 -0
- package/src/services/ipa/users.ts +794 -0
- package/src/services/logging/index.ts +294 -0
- package/src/services/notifications/email.ts +123 -0
- package/src/services/notifications/index.ts +413 -0
- package/src/services/postgres.ts +51 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +13 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +255 -0
- package/src/services/session/index.ts +137 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +101 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +824 -0
- package/src/services/settings/index.ts +203 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +179 -0
- package/src/services/settings/templates.ts +10 -0
- package/src/services/weather/forecast.ts +287 -0
- package/src/services/weather/geo.ts +110 -0
- package/src/services/weather/index.ts +99 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +125 -0
- package/src/services/weather/migrate.ts +22 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/shared/account-display.ts +17 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/icons.ts +109 -0
- package/src/shared/index.ts +10 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +58 -0
- package/src/shared/markdown/extensions/images.ts +43 -0
- package/src/shared/markdown/extensions/info-blocks.ts +93 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +34 -0
- package/src/shared/markdown/extensions/tables.ts +88 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/index.ts +97 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/ssr/AdminLayout.tsx +42 -0
- package/src/ssr/AdminSidebar.tsx +95 -0
- package/src/ssr/Footer.island.tsx +62 -0
- package/src/ssr/GlobalSearchDialog.tsx +389 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +106 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +42 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +99 -0
- package/src/ssr/Layout.tsx +326 -0
- package/src/ssr/MoreAppsDropdown.island.tsx +61 -0
- package/src/ssr/NavMenu.island.tsx +108 -0
- package/src/ssr/ThemeToggleRail.island.tsx +27 -0
- package/src/ssr/index.ts +5 -0
- package/src/ssr/islands/SearchBar.island.tsx +77 -0
- package/src/ssr/islands/index.ts +1 -0
- package/src/ssr/runtime.ts +22 -0
- package/src/styles/base-popover.css +28 -0
- package/src/styles/effects.css +65 -0
- package/src/styles/global.css +133 -0
- package/src/styles/input.css +54 -0
- package/src/styles/tokens.css +35 -0
- package/src/styles/utilities-buttons.css +125 -0
- package/src/styles/utilities-feedback.css +65 -0
- package/src/styles/utilities-layout.css +122 -0
- package/src/styles/utilities-navigation.css +196 -0
- package/src/types/ambient.d.ts +8 -0
- package/src/ui/admin-settings.tsx +148 -0
- package/src/ui/dialog-core.ts +146 -0
- package/src/ui/filter/FilterChip.tsx +196 -0
- package/src/ui/filter/index.ts +2 -0
- package/src/ui/index.ts +19 -0
- package/src/ui/input/Checkbox.tsx +55 -0
- package/src/ui/input/ColorInput.tsx +122 -0
- package/src/ui/input/DateTimeInput.tsx +86 -0
- package/src/ui/input/ImageInput.tsx +170 -0
- package/src/ui/input/NumberInput.tsx +113 -0
- package/src/ui/input/PinInput.tsx +169 -0
- package/src/ui/input/SegmentedControl.tsx +99 -0
- package/src/ui/input/Select.tsx +288 -0
- package/src/ui/input/SelectChip.tsx +61 -0
- package/src/ui/input/Slider.tsx +118 -0
- package/src/ui/input/Switch.tsx +62 -0
- package/src/ui/input/TagsInput.tsx +115 -0
- package/src/ui/input/TextInput.tsx +160 -0
- package/src/ui/input/index.ts +13 -0
- package/src/ui/input/types.ts +42 -0
- package/src/ui/input/util.tsx +105 -0
- package/src/ui/ipa/Avatar.tsx +28 -0
- package/src/ui/ipa/GroupView.tsx +36 -0
- package/src/ui/ipa/LoginBtn.tsx +16 -0
- package/src/ui/ipa/UserView.tsx +58 -0
- package/src/ui/ipa/index.ts +4 -0
- package/src/ui/misc/ContextMenu.tsx +211 -0
- package/src/ui/misc/CopyButton.tsx +28 -0
- package/src/ui/misc/Dropdown.tsx +194 -0
- package/src/ui/misc/EntitySearch.tsx +213 -0
- package/src/ui/misc/Lightbox.tsx +194 -0
- package/src/ui/misc/LinkCard.tsx +34 -0
- package/src/ui/misc/LogEntriesTable.tsx +61 -0
- package/src/ui/misc/MarkdownView.tsx +65 -0
- package/src/ui/misc/Pagination.tsx +51 -0
- package/src/ui/misc/PermissionEditor.tsx +379 -0
- package/src/ui/misc/ProgressBar.tsx +47 -0
- package/src/ui/misc/RemoveBtn.tsx +27 -0
- package/src/ui/misc/StatCell.tsx +90 -0
- package/src/ui/misc/index.ts +18 -0
- package/src/ui/navigation.ts +32 -0
- package/src/ui/prompts.tsx +854 -0
- package/src/ui/sidebar.tsx +468 -0
- package/src/ui/widgets/Widget.tsx +62 -0
- package/src/ui/widgets/WidgetCard.tsx +19 -0
- package/src/ui/widgets/WidgetHero.tsx +39 -0
- package/src/ui/widgets/WidgetList.tsx +84 -0
- package/src/ui/widgets/WidgetPills.tsx +68 -0
- package/src/ui/widgets/WidgetStat.tsx +67 -0
- package/src/ui/widgets/WidgetStatus.tsx +62 -0
- package/src/ui/widgets/index.ts +9 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { providers } from "../providers";
|
|
2
|
+
import { search } from "./search";
|
|
3
|
+
|
|
4
|
+
export const ipa = {
|
|
5
|
+
auth: providers.ipa.auth,
|
|
6
|
+
users: {
|
|
7
|
+
...providers.ipa.users,
|
|
8
|
+
addIpa: providers.ipa.users.create,
|
|
9
|
+
delete: providers.ipa.users.remove,
|
|
10
|
+
},
|
|
11
|
+
groups: {
|
|
12
|
+
...providers.ipa.groups,
|
|
13
|
+
delete: providers.ipa.groups.remove,
|
|
14
|
+
},
|
|
15
|
+
search,
|
|
16
|
+
sync: providers.ipa.sync,
|
|
17
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized profile calculation for IPA-backed users.
|
|
3
|
+
*
|
|
4
|
+
* The calculation uses the local mirrored IPA group tree only and treats
|
|
5
|
+
* `auth.groups.id` as the canonical group identity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sql } from "bun";
|
|
9
|
+
import { calculateIpaProfileFromGroupNames } from "../account-model";
|
|
10
|
+
import { getFreeIpaConfig } from "../freeipa-config";
|
|
11
|
+
|
|
12
|
+
type DbRow = Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get all IPA group names a user belongs to (direct + inherited via parent groups).
|
|
16
|
+
*/
|
|
17
|
+
export const getAllUserGroups = async (userId: string): Promise<string[]> => {
|
|
18
|
+
const rows: DbRow[] = await sql`
|
|
19
|
+
WITH RECURSIVE all_groups AS (
|
|
20
|
+
SELECT ug.group_id
|
|
21
|
+
FROM auth.user_groups_v2 ug
|
|
22
|
+
JOIN auth.groups g ON g.id = ug.group_id
|
|
23
|
+
WHERE ug.user_id = ${userId} AND g.provider = 'ipa'
|
|
24
|
+
UNION
|
|
25
|
+
SELECT gg.parent_group_id
|
|
26
|
+
FROM auth.group_groups_v2 gg
|
|
27
|
+
JOIN auth.groups g ON g.id = gg.parent_group_id
|
|
28
|
+
JOIN all_groups ag ON gg.child_group_id = ag.group_id
|
|
29
|
+
WHERE g.provider = 'ipa'
|
|
30
|
+
)
|
|
31
|
+
SELECT DISTINCT g.name
|
|
32
|
+
FROM all_groups ag
|
|
33
|
+
JOIN auth.groups g ON g.id = ag.group_id
|
|
34
|
+
ORDER BY g.name
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
return rows.map((row) => row.name as string);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Calculate canonical IPA profile from effective group names. Reads
|
|
42
|
+
* `freeipa.groups.base_ipa_realm` from settings (cache-aside).
|
|
43
|
+
*/
|
|
44
|
+
export const calculateIpaProfile = async (memberOfGroups: string[]): Promise<"user" | "guest"> => {
|
|
45
|
+
const config = await getFreeIpaConfig();
|
|
46
|
+
return calculateIpaProfileFromGroupNames(memberOfGroups, config.groupsBaseIpaRealm);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Calculate canonical IPA profile for a user from the local DB mirror.
|
|
51
|
+
*/
|
|
52
|
+
export const calculateIpaProfileFromLocalDb = async (userId: string): Promise<"user" | "guest"> => {
|
|
53
|
+
const groups = await getAllUserGroups(userId);
|
|
54
|
+
return calculateIpaProfile(groups);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Update one IPA-backed user's canonical profile projection.
|
|
59
|
+
*/
|
|
60
|
+
export const updateUserIpaProfile = async (userId: string): Promise<void> => {
|
|
61
|
+
const profile = await calculateIpaProfileFromLocalDb(userId);
|
|
62
|
+
await sql`
|
|
63
|
+
UPDATE auth.users
|
|
64
|
+
SET provider = 'ipa',
|
|
65
|
+
profile = ${profile}
|
|
66
|
+
WHERE id = ${userId} AND provider = 'ipa'
|
|
67
|
+
`;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update all IPA-backed users affected by a group hierarchy change.
|
|
72
|
+
*/
|
|
73
|
+
export const updateProfileForAffectedUsers = async (groupId: string): Promise<void> => {
|
|
74
|
+
const affectedUsers: DbRow[] = await sql`
|
|
75
|
+
WITH RECURSIVE affected_groups AS (
|
|
76
|
+
SELECT ${groupId}::uuid AS group_id
|
|
77
|
+
UNION
|
|
78
|
+
SELECT gg.child_group_id
|
|
79
|
+
FROM auth.group_groups_v2 gg
|
|
80
|
+
JOIN affected_groups ag ON gg.parent_group_id = ag.group_id
|
|
81
|
+
)
|
|
82
|
+
SELECT DISTINCT ug.user_id
|
|
83
|
+
FROM auth.user_groups_v2 ug
|
|
84
|
+
JOIN affected_groups ag ON ug.group_id = ag.group_id
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
for (const row of affectedUsers) {
|
|
88
|
+
await updateUserIpaProfile(row.user_id as string);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { BaseUser, BaseGroup, UserProvider, UserProfile } from "../../contracts/shared";
|
|
3
|
+
import { buildRoles } from "../account-model";
|
|
4
|
+
import { freeipa } from "../../server/services";
|
|
5
|
+
import { toPgTextArray, toPgUuidArray } from "../postgres";
|
|
6
|
+
import { getFreeIpaConfig } from "../freeipa-config";
|
|
7
|
+
|
|
8
|
+
// ==========================
|
|
9
|
+
// Search Options
|
|
10
|
+
// ==========================
|
|
11
|
+
|
|
12
|
+
export type SearchOptions = {
|
|
13
|
+
/** Search users */
|
|
14
|
+
users?: boolean;
|
|
15
|
+
/** Search groups */
|
|
16
|
+
groups?: boolean;
|
|
17
|
+
/** User UUIDs to exclude from results */
|
|
18
|
+
excludeUserIds?: string[];
|
|
19
|
+
/** Group IDs to exclude from results */
|
|
20
|
+
excludeGroups?: string[];
|
|
21
|
+
/** Only return groups the user is a member of */
|
|
22
|
+
onlyUserGroups?: string[];
|
|
23
|
+
/** Only return POSIX groups (have gid_number) */
|
|
24
|
+
onlyPosixGroups?: boolean;
|
|
25
|
+
/** Only return users that are members of these groups */
|
|
26
|
+
usersInGroups?: string[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ==========================
|
|
30
|
+
// Search (autocomplete for member/manager add dialogs)
|
|
31
|
+
// Returns BaseUser/BaseGroup (no relations needed for autocomplete)
|
|
32
|
+
// ==========================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Executes a filtered lookup query and returns normalized matches.
|
|
36
|
+
*/
|
|
37
|
+
export const search = async (query: string, options: SearchOptions): Promise<{ users: BaseUser[]; groups: BaseGroup[] }> => {
|
|
38
|
+
const q = `%${freeipa.util.escapeLike(query.toLowerCase())}%`;
|
|
39
|
+
let users: BaseUser[] = [];
|
|
40
|
+
let groups: BaseGroup[] = [];
|
|
41
|
+
|
|
42
|
+
// ========== Search Users ==========
|
|
43
|
+
if (options.users) {
|
|
44
|
+
const excludeIds = options.excludeUserIds ?? [];
|
|
45
|
+
const inGroups = options.usersInGroups ?? [];
|
|
46
|
+
const groupsAdmin = (await getFreeIpaConfig()).groupsAdmin;
|
|
47
|
+
|
|
48
|
+
// Build optional WHERE fragments
|
|
49
|
+
const excludeFilter = excludeIds.length > 0 ? sql`AND u.id <> ALL(${toPgUuidArray(excludeIds)}::uuid[])` : sql``;
|
|
50
|
+
const groupFilter =
|
|
51
|
+
inGroups.length > 0
|
|
52
|
+
? sql`AND EXISTS (
|
|
53
|
+
SELECT 1
|
|
54
|
+
FROM auth.user_groups_v2 ug
|
|
55
|
+
JOIN auth.groups g ON g.id = ug.group_id
|
|
56
|
+
WHERE ug.user_id = u.id
|
|
57
|
+
AND g.provider = 'ipa'
|
|
58
|
+
AND ug.group_id = ANY(${toPgUuidArray(inGroups)}::uuid[])
|
|
59
|
+
)`
|
|
60
|
+
: sql``;
|
|
61
|
+
|
|
62
|
+
const rows = await sql`
|
|
63
|
+
SELECT u.id, u.uid, u.provider, u.profile, u.given_name, u.sn, u.display_name, u.mail,
|
|
64
|
+
EXISTS(
|
|
65
|
+
SELECT 1
|
|
66
|
+
FROM auth.user_groups_v2 ug_admin
|
|
67
|
+
JOIN auth.groups g_admin ON g_admin.id = ug_admin.group_id
|
|
68
|
+
WHERE ug_admin.user_id = u.id
|
|
69
|
+
AND g_admin.provider = 'ipa'
|
|
70
|
+
AND g_admin.name = ANY(${toPgTextArray(groupsAdmin)}::text[])
|
|
71
|
+
) AS effective_admin
|
|
72
|
+
FROM auth.users u
|
|
73
|
+
WHERE u.provider = 'ipa'
|
|
74
|
+
AND (
|
|
75
|
+
LOWER(u.uid) LIKE ${q} ESCAPE '\\' OR LOWER(u.display_name) LIKE ${q} ESCAPE '\\' OR
|
|
76
|
+
LOWER(u.given_name) LIKE ${q} ESCAPE '\\' OR LOWER(u.sn) LIKE ${q} ESCAPE '\\' OR LOWER(u.mail) LIKE ${q} ESCAPE '\\'
|
|
77
|
+
)
|
|
78
|
+
${excludeFilter}
|
|
79
|
+
${groupFilter}
|
|
80
|
+
ORDER BY u.uid
|
|
81
|
+
LIMIT 10
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
type UserRow = {
|
|
85
|
+
id: string;
|
|
86
|
+
uid: string;
|
|
87
|
+
provider: UserProvider;
|
|
88
|
+
profile: UserProfile;
|
|
89
|
+
effective_admin: boolean | null;
|
|
90
|
+
given_name: string | null;
|
|
91
|
+
sn: string | null;
|
|
92
|
+
display_name: string | null;
|
|
93
|
+
mail: string | null;
|
|
94
|
+
};
|
|
95
|
+
users = (rows as UserRow[]).map((row) => ({
|
|
96
|
+
id: row.id,
|
|
97
|
+
uid: row.uid,
|
|
98
|
+
roles: buildRoles({
|
|
99
|
+
provider: row.provider,
|
|
100
|
+
profile: row.profile,
|
|
101
|
+
memberofGroup: [],
|
|
102
|
+
manages: [],
|
|
103
|
+
admin: Boolean(row.effective_admin),
|
|
104
|
+
}),
|
|
105
|
+
provider: row.provider,
|
|
106
|
+
profile: row.profile,
|
|
107
|
+
givenname: row.given_name ?? "",
|
|
108
|
+
sn: row.sn ?? "",
|
|
109
|
+
displayName: row.display_name ?? "",
|
|
110
|
+
mail: row.mail ?? null,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ========== Search Groups ==========
|
|
115
|
+
if (options.groups) {
|
|
116
|
+
const excludeIds = options.excludeGroups ?? [];
|
|
117
|
+
const onlyUserGroups = options.onlyUserGroups ?? [];
|
|
118
|
+
const onlyPosix = options.onlyPosixGroups ?? false;
|
|
119
|
+
|
|
120
|
+
// Build optional WHERE fragments
|
|
121
|
+
const excludeFilter = excludeIds.length > 0 ? sql`AND id <> ALL(${toPgUuidArray(excludeIds)}::uuid[])` : sql``;
|
|
122
|
+
const userGroupsFilter = onlyUserGroups.length > 0 ? sql`AND id = ANY(${toPgUuidArray(onlyUserGroups)}::uuid[])` : sql``;
|
|
123
|
+
const posixFilter = onlyPosix ? sql`AND gid_number IS NOT NULL` : sql``;
|
|
124
|
+
|
|
125
|
+
const rows = await sql`
|
|
126
|
+
SELECT id, provider, name, description, gid_number
|
|
127
|
+
FROM auth.groups
|
|
128
|
+
WHERE provider = 'ipa'
|
|
129
|
+
AND (LOWER(name) LIKE ${q} ESCAPE '\\' OR LOWER(description) LIKE ${q} ESCAPE '\\')
|
|
130
|
+
${excludeFilter}
|
|
131
|
+
${userGroupsFilter}
|
|
132
|
+
${posixFilter}
|
|
133
|
+
ORDER BY name
|
|
134
|
+
LIMIT 10
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
type GroupRow = {
|
|
138
|
+
id: string;
|
|
139
|
+
provider: UserProvider;
|
|
140
|
+
name: string;
|
|
141
|
+
description: string | null;
|
|
142
|
+
gid_number: number | null;
|
|
143
|
+
};
|
|
144
|
+
groups = (rows as GroupRow[]).map((row) => ({
|
|
145
|
+
id: row.id,
|
|
146
|
+
provider: row.provider,
|
|
147
|
+
name: row.name,
|
|
148
|
+
description: row.description ?? null,
|
|
149
|
+
gidnumber: row.gid_number ?? null,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { users, groups };
|
|
154
|
+
};
|