@fuzdev/fuz_app 0.29.0 → 0.31.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/dist/actions/CLAUDE.md +630 -0
- package/dist/actions/action_rpc.d.ts +29 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +42 -6
- package/dist/actions/action_types.d.ts +2 -2
- package/dist/actions/cancel.d.ts +12 -13
- package/dist/actions/cancel.d.ts.map +1 -1
- package/dist/actions/cancel.js +10 -13
- package/dist/actions/heartbeat.d.ts +8 -13
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -8
- package/dist/actions/register_action_ws.d.ts +3 -3
- package/dist/actions/register_action_ws.js +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +4 -4
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -3
- package/dist/actions/socket.svelte.d.ts +16 -16
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +15 -15
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.d.ts +15 -0
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +17 -0
- package/dist/auth/CLAUDE.md +923 -0
- package/dist/auth/account_action_specs.d.ts +216 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -0
- package/dist/auth/account_action_specs.js +159 -0
- package/dist/auth/account_actions.d.ts +51 -0
- package/dist/auth/account_actions.d.ts.map +1 -0
- package/dist/auth/account_actions.js +119 -0
- package/dist/auth/account_queries.d.ts +6 -2
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +40 -4
- package/dist/auth/account_routes.d.ts +94 -16
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +108 -180
- package/dist/auth/account_schema.d.ts +85 -30
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +40 -8
- package/dist/auth/admin_action_specs.d.ts +674 -0
- package/dist/auth/admin_action_specs.d.ts.map +1 -0
- package/dist/auth/admin_action_specs.js +287 -0
- package/dist/auth/admin_actions.d.ts +69 -0
- package/dist/auth/admin_actions.d.ts.map +1 -0
- package/dist/auth/admin_actions.js +256 -0
- package/dist/auth/api_token.d.ts +10 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +9 -0
- package/dist/auth/api_token_queries.d.ts +3 -3
- package/dist/auth/api_token_queries.js +3 -3
- package/dist/auth/app_settings_schema.d.ts +4 -3
- package/dist/auth/app_settings_schema.d.ts.map +1 -1
- package/dist/auth/app_settings_schema.js +2 -1
- package/dist/auth/audit_log_routes.d.ts +14 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +22 -79
- package/dist/auth/audit_log_schema.d.ts +100 -29
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +83 -11
- package/dist/auth/bootstrap_routes.d.ts +14 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +10 -3
- package/dist/auth/cleanup.d.ts +63 -0
- package/dist/auth/cleanup.d.ts.map +1 -0
- package/dist/auth/cleanup.js +80 -0
- package/dist/auth/invite_schema.d.ts +11 -10
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +4 -3
- package/dist/auth/migrations.d.ts +6 -0
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +28 -0
- package/dist/auth/permit_offer_action_specs.d.ts +364 -0
- package/dist/auth/permit_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/permit_offer_action_specs.js +216 -0
- package/dist/auth/permit_offer_actions.d.ts +96 -0
- package/dist/auth/permit_offer_actions.d.ts.map +1 -0
- package/dist/auth/permit_offer_actions.js +428 -0
- package/dist/auth/permit_offer_notifications.d.ts +361 -0
- package/dist/auth/permit_offer_notifications.d.ts.map +1 -0
- package/dist/auth/permit_offer_notifications.js +179 -0
- package/dist/auth/permit_offer_queries.d.ts +165 -0
- package/dist/auth/permit_offer_queries.d.ts.map +1 -0
- package/dist/auth/permit_offer_queries.js +390 -0
- package/dist/auth/permit_offer_schema.d.ts +103 -0
- package/dist/auth/permit_offer_schema.d.ts.map +1 -0
- package/dist/auth/permit_offer_schema.js +142 -0
- package/dist/auth/permit_queries.d.ts +77 -14
- package/dist/auth/permit_queries.d.ts.map +1 -1
- package/dist/auth/permit_queries.js +119 -24
- package/dist/auth/session_queries.d.ts +4 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +4 -2
- package/dist/auth/signup_routes.d.ts +13 -0
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +14 -7
- package/dist/http/CLAUDE.md +584 -0
- package/dist/http/pending_effects.d.ts +29 -0
- package/dist/http/pending_effects.d.ts.map +1 -0
- package/dist/http/pending_effects.js +31 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +4 -3
- package/dist/rate_limiter.d.ts +30 -0
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +25 -2
- package/dist/realtime/sse_auth_guard.d.ts +2 -0
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +5 -3
- package/dist/testing/CLAUDE.md +668 -1
- package/dist/testing/admin_integration.d.ts +10 -7
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +382 -482
- package/dist/testing/app_server.d.ts +7 -6
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +9 -3
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +4 -4
- package/dist/testing/audit_completeness.d.ts +6 -0
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +158 -134
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +4 -33
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +2 -0
- package/dist/testing/entities.d.ts +35 -13
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +17 -0
- package/dist/testing/integration.d.ts +10 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +352 -340
- package/dist/testing/integration_helpers.d.ts +16 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +24 -4
- package/dist/testing/rate_limiting.d.ts +7 -0
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +41 -10
- package/dist/testing/rpc_helpers.d.ts +153 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +184 -8
- package/dist/testing/sse_round_trip.d.ts +8 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +10 -3
- package/dist/testing/standard.d.ts +9 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +6 -2
- package/dist/testing/surface_invariants.d.ts +7 -3
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +5 -4
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +9 -38
- package/dist/ui/AccountSessions.svelte +8 -4
- package/dist/ui/AccountSessions.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAccounts.svelte +61 -33
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminAuditLog.svelte +3 -2
- package/dist/ui/AdminAuditLog.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -2
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +14 -9
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminPermitHistory.svelte +3 -2
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +29 -25
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +351 -0
- package/dist/ui/OpenSignupToggle.svelte +6 -3
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/PermitOfferForm.svelte +141 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts +14 -0
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferHistory.svelte +109 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts +11 -0
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/PermitOfferInbox.svelte +121 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts +12 -0
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/account_sessions_state.svelte.d.ts +53 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +39 -16
- package/dist/ui/admin_accounts_state.svelte.d.ts +118 -2
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +99 -23
- package/dist/ui/admin_invites_state.svelte.d.ts +47 -1
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +38 -26
- package/dist/ui/admin_sessions_state.svelte.d.ts +26 -0
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +35 -21
- package/dist/ui/app_settings_state.svelte.d.ts +39 -0
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +34 -18
- package/dist/ui/audit_log_state.svelte.d.ts +40 -3
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +36 -42
- package/dist/ui/auth_state.svelte.d.ts +4 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +4 -1
- package/dist/ui/permit_offers_state.svelte.d.ts +125 -0
- package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/permit_offers_state.svelte.js +197 -0
- package/package.json +3 -3
- package/dist/auth/admin_routes.d.ts +0 -29
- package/dist/auth/admin_routes.d.ts.map +0 -1
- package/dist/auth/admin_routes.js +0 -226
- package/dist/auth/app_settings_routes.d.ts +0 -27
- package/dist/auth/app_settings_routes.d.ts.map +0 -1
- package/dist/auth/app_settings_routes.js +0 -66
- package/dist/auth/invite_routes.d.ts +0 -18
- package/dist/auth/invite_routes.d.ts.map +0 -1
- package/dist/auth/invite_routes.js +0 -129
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Recipient-side pending offer inbox.
|
|
4
|
+
*
|
|
5
|
+
* Renders `PermitOffersState.incoming` (pending, soonest-expiry first)
|
|
6
|
+
* with accept + decline-with-reason controls. Grantor and scope rendering
|
|
7
|
+
* are delegated via optional callback props — consumers plug in display
|
|
8
|
+
* names once they know what a `from_actor_id` / `scope_id` represents
|
|
9
|
+
* in their domain (usernames, classroom names, etc.).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import PendingButton from '@fuzdev/fuz_ui/PendingButton.svelte';
|
|
13
|
+
import {SvelteMap} from 'svelte/reactivity';
|
|
14
|
+
|
|
15
|
+
import {permit_offers_state_context} from './permit_offers_state.svelte.js';
|
|
16
|
+
import ConfirmButton from './ConfirmButton.svelte';
|
|
17
|
+
import {format_relative_time, format_datetime_local, truncate_uuid} from './ui_format.js';
|
|
18
|
+
import {PERMIT_OFFER_MESSAGE_LENGTH_MAX} from '../auth/permit_offer_schema.js';
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
format_actor = truncate_uuid,
|
|
22
|
+
format_scope,
|
|
23
|
+
format_role = (role: string) => role,
|
|
24
|
+
}: {
|
|
25
|
+
/** Display label for `from_actor_id`. Defaults to a truncated uuid. */
|
|
26
|
+
format_actor?: (from_actor_id: string) => string;
|
|
27
|
+
/** Display label for an offer's scope. Defaults to truncated uuid or "global" when `null`. */
|
|
28
|
+
format_scope?: (scope_id: string | null, role: string) => string;
|
|
29
|
+
/** Display label for a role constant. Defaults to identity (role name as stored). */
|
|
30
|
+
format_role?: (role: string) => string;
|
|
31
|
+
} = $props();
|
|
32
|
+
|
|
33
|
+
const permit_offers = permit_offers_state_context.get();
|
|
34
|
+
|
|
35
|
+
const scope_label = (scope_id: string | null, role: string): string => {
|
|
36
|
+
if (format_scope) return format_scope(scope_id, role);
|
|
37
|
+
return scope_id === null ? 'global' : truncate_uuid(scope_id);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const decline_reasons: SvelteMap<string, string> = new SvelteMap();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<section class="permit-offer-inbox">
|
|
44
|
+
<h2>pending offers</h2>
|
|
45
|
+
|
|
46
|
+
{#if permit_offers.error}
|
|
47
|
+
<p class="color_c_50">{permit_offers.error}</p>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
{#if permit_offers.incoming.length === 0}
|
|
51
|
+
<p class="text_50">No pending offers.</p>
|
|
52
|
+
{:else}
|
|
53
|
+
<ul class="column gap_md">
|
|
54
|
+
{#each permit_offers.incoming as offer (offer.id)}
|
|
55
|
+
<li class="box p_md column gap_sm">
|
|
56
|
+
<div class="row gap_sm align_center">
|
|
57
|
+
<span class="chip color_a">{format_role(offer.role)}</span>
|
|
58
|
+
<span class="text_50 font_size_sm">{scope_label(offer.scope_id, offer.role)}</span>
|
|
59
|
+
<span class="text_50 font_size_sm">from {format_actor(offer.from_actor_id)}</span>
|
|
60
|
+
<span
|
|
61
|
+
class="text_50 font_size_sm ml_auto"
|
|
62
|
+
title={format_datetime_local(offer.expires_at)}
|
|
63
|
+
>
|
|
64
|
+
expires {format_relative_time(offer.expires_at)}
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{#if offer.message}
|
|
69
|
+
<p class="mb_0">{offer.message}</p>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
72
|
+
<div class="row gap_sm">
|
|
73
|
+
<PendingButton
|
|
74
|
+
pending={permit_offers.loading}
|
|
75
|
+
disabled={permit_offers.loading}
|
|
76
|
+
onclick={() => permit_offers.accept(offer.id)}
|
|
77
|
+
class="color_b"
|
|
78
|
+
>
|
|
79
|
+
accept
|
|
80
|
+
</PendingButton>
|
|
81
|
+
|
|
82
|
+
<ConfirmButton
|
|
83
|
+
title="decline offer"
|
|
84
|
+
position="bottom"
|
|
85
|
+
onconfirm={() => {
|
|
86
|
+
const reason = decline_reasons.get(offer.id) ?? '';
|
|
87
|
+
void permit_offers.decline(offer.id, reason || null);
|
|
88
|
+
decline_reasons.delete(offer.id);
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{#snippet children(_popover, _confirm)}
|
|
92
|
+
decline
|
|
93
|
+
{/snippet}
|
|
94
|
+
{#snippet popover_content(popover, confirm)}
|
|
95
|
+
<div class="column gap_sm p_sm">
|
|
96
|
+
<label>
|
|
97
|
+
<div class="title">reason (optional)</div>
|
|
98
|
+
<textarea
|
|
99
|
+
name="decline-reason"
|
|
100
|
+
maxlength={PERMIT_OFFER_MESSAGE_LENGTH_MAX}
|
|
101
|
+
placeholder="optional reason"
|
|
102
|
+
value={decline_reasons.get(offer.id) ?? ''}
|
|
103
|
+
oninput={(e) =>
|
|
104
|
+
decline_reasons.set(offer.id, (e.target as HTMLTextAreaElement).value)}
|
|
105
|
+
></textarea>
|
|
106
|
+
</label>
|
|
107
|
+
<div class="row gap_sm">
|
|
108
|
+
<button type="button" class="color_c bg_100" onclick={confirm}>
|
|
109
|
+
confirm decline
|
|
110
|
+
</button>
|
|
111
|
+
<button type="button" onclick={() => popover.hide()}>cancel</button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
{/snippet}
|
|
115
|
+
</ConfirmButton>
|
|
116
|
+
</div>
|
|
117
|
+
</li>
|
|
118
|
+
{/each}
|
|
119
|
+
</ul>
|
|
120
|
+
{/if}
|
|
121
|
+
</section>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** Display label for `from_actor_id`. Defaults to a truncated uuid. */
|
|
3
|
+
format_actor?: (from_actor_id: string) => string;
|
|
4
|
+
/** Display label for an offer's scope. Defaults to truncated uuid or "global" when `null`. */
|
|
5
|
+
format_scope?: (scope_id: string | null, role: string) => string;
|
|
6
|
+
/** Display label for a role constant. Defaults to identity (role name as stored). */
|
|
7
|
+
format_role?: (role: string) => string;
|
|
8
|
+
};
|
|
9
|
+
declare const PermitOfferInbox: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type PermitOfferInbox = ReturnType<typeof PermitOfferInbox>;
|
|
11
|
+
export default PermitOfferInbox;
|
|
12
|
+
//# sourceMappingURL=PermitOfferInbox.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermitOfferInbox.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/PermitOfferInbox.svelte"],"names":[],"mappings":"AAoBC,KAAK,gBAAgB,GAAI;IACxB,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjE,qFAAqF;IACrF,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;AA0FH,QAAA,MAAM,gBAAgB,sDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -1,13 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reactive state for managing auth sessions on a
|
|
2
|
+
* Reactive state for managing the authenticated account's auth sessions on a
|
|
3
|
+
* settings page. Reads and mutations flow through a narrow RPC adapter; the
|
|
4
|
+
* REST routes that backed this class moved to `account_actions.ts` in the
|
|
5
|
+
* 2026-04-23 RPC migration.
|
|
3
6
|
*
|
|
4
7
|
* @module
|
|
5
8
|
*/
|
|
6
9
|
import { Loadable } from './loadable.svelte.js';
|
|
7
|
-
import type {
|
|
10
|
+
import type { AuthSessionJson } from '../auth/account_schema.js';
|
|
11
|
+
/**
|
|
12
|
+
* Narrow RPC surface consumed by `AccountSessionsState`. Consumers adapt their
|
|
13
|
+
* typed RPC client to this shape. Mirrors the other per-domain `*Rpc`
|
|
14
|
+
* interfaces (`AdminAccountsRpc`, `AuditLogRpc`, `AdminInvitesRpc`).
|
|
15
|
+
*
|
|
16
|
+
* The three methods wrap the corresponding action specs on
|
|
17
|
+
* `account_actions.ts`:
|
|
18
|
+
*
|
|
19
|
+
* - `list` → `account_session_list`
|
|
20
|
+
* - `revoke` → `account_session_revoke` (IDOR-guarded by `account_id` server-side)
|
|
21
|
+
* - `revoke_all` → `account_session_revoke_all`
|
|
22
|
+
*/
|
|
23
|
+
export interface AccountSessionsRpc {
|
|
24
|
+
list: () => Promise<{
|
|
25
|
+
sessions: Array<AuthSessionJson>;
|
|
26
|
+
}>;
|
|
27
|
+
revoke: (params: {
|
|
28
|
+
session_id: string;
|
|
29
|
+
}) => Promise<{
|
|
30
|
+
ok: true;
|
|
31
|
+
revoked: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
revoke_all: () => Promise<{
|
|
34
|
+
ok: true;
|
|
35
|
+
count: number;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Svelte context carrying the reactive `AccountSessionsRpc` accessor. Mirrors
|
|
40
|
+
* the admin-side RPC contexts. Unset context falls back to `() => null` so
|
|
41
|
+
* components render the usual "rpc adapter not wired" state.
|
|
42
|
+
*/
|
|
43
|
+
export declare const account_sessions_rpc_context: {
|
|
44
|
+
get: () => () => AccountSessionsRpc | null;
|
|
45
|
+
set: (value?: (() => AccountSessionsRpc | null) | undefined) => () => AccountSessionsRpc | null;
|
|
46
|
+
};
|
|
47
|
+
export interface AccountSessionsStateOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Reactive accessor for the RPC adapter; returns `null` when unwired.
|
|
50
|
+
* Matches the `get_rpc` pattern on the admin state classes.
|
|
51
|
+
*/
|
|
52
|
+
get_rpc?: () => AccountSessionsRpc | null;
|
|
53
|
+
}
|
|
8
54
|
export declare class AccountSessionsState extends Loadable {
|
|
9
|
-
|
|
55
|
+
#private;
|
|
56
|
+
sessions: Array<AuthSessionJson>;
|
|
10
57
|
readonly active_count: number;
|
|
58
|
+
constructor(options?: AccountSessionsStateOptions);
|
|
59
|
+
/** True when an RPC adapter is wired. `fetch` / `revoke` / `revoke_all` no-op without it. */
|
|
60
|
+
get has_rpc(): boolean;
|
|
11
61
|
fetch(): Promise<void>;
|
|
12
62
|
revoke(id: string): Promise<void>;
|
|
13
63
|
revoke_all(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/account_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"account_sessions_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/account_sessions_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC;QAAC,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;KAAC,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,MAAM,EAAE;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IAChF,UAAU,EAAE,MAAM,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;qBAAwB,kBAAkB,GAAG,IAAI;yBAAzB,kBAAkB,GAAG,IAAI,wBAAzB,kBAAkB,GAAG,IAAI;CAEzF,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC3C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,kBAAkB,GAAG,IAAI,CAAC;CAC1C;AAED,qBAAa,oBAAqB,SAAQ,QAAQ;;IAGjD,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAkB;IAElD,QAAQ,CAAC,YAAY,SAAkC;gBAE3C,OAAO,CAAC,EAAE,2BAA2B;IAKjD,6FAA6F;IAC7F,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAejC"}
|
|
@@ -1,40 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reactive state for managing auth sessions on a
|
|
2
|
+
* Reactive state for managing the authenticated account's auth sessions on a
|
|
3
|
+
* settings page. Reads and mutations flow through a narrow RPC adapter; the
|
|
4
|
+
* REST routes that backed this class moved to `account_actions.ts` in the
|
|
5
|
+
* 2026-04-23 RPC migration.
|
|
3
6
|
*
|
|
4
7
|
* @module
|
|
5
8
|
*/
|
|
9
|
+
import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
|
|
6
10
|
import { Loadable } from './loadable.svelte.js';
|
|
7
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Svelte context carrying the reactive `AccountSessionsRpc` accessor. Mirrors
|
|
13
|
+
* the admin-side RPC contexts. Unset context falls back to `() => null` so
|
|
14
|
+
* components render the usual "rpc adapter not wired" state.
|
|
15
|
+
*/
|
|
16
|
+
export const account_sessions_rpc_context = create_context(() => () => null);
|
|
8
17
|
export class AccountSessionsState extends Loadable {
|
|
18
|
+
#get_rpc;
|
|
9
19
|
sessions = $state.raw([]);
|
|
10
20
|
active_count = $derived(this.sessions.length);
|
|
21
|
+
constructor(options) {
|
|
22
|
+
super();
|
|
23
|
+
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
24
|
+
}
|
|
25
|
+
/** True when an RPC adapter is wired. `fetch` / `revoke` / `revoke_all` no-op without it. */
|
|
26
|
+
get has_rpc() {
|
|
27
|
+
return this.#get_rpc() !== null;
|
|
28
|
+
}
|
|
11
29
|
async fetch() {
|
|
30
|
+
const rpc = this.#get_rpc();
|
|
31
|
+
if (!rpc) {
|
|
32
|
+
this.error = 'rpc adapter not wired';
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
12
35
|
await this.run(async () => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
throw new Error(await parse_response_error(response, 'Failed to fetch sessions'));
|
|
16
|
-
}
|
|
17
|
-
const data = await response.json();
|
|
18
|
-
this.sessions = data.sessions ?? [];
|
|
36
|
+
const { sessions } = await rpc.list();
|
|
37
|
+
this.sessions = sessions;
|
|
19
38
|
});
|
|
20
39
|
}
|
|
21
40
|
async revoke(id) {
|
|
41
|
+
const rpc = this.#get_rpc();
|
|
42
|
+
if (!rpc) {
|
|
43
|
+
this.error = 'rpc adapter not wired';
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
22
46
|
await this.run(async () => {
|
|
23
|
-
|
|
24
|
-
if (!response.ok) {
|
|
25
|
-
throw new Error(await parse_response_error(response, 'Failed to revoke session'));
|
|
26
|
-
}
|
|
47
|
+
await rpc.revoke({ session_id: id });
|
|
27
48
|
});
|
|
28
49
|
if (!this.error) {
|
|
29
50
|
await this.fetch();
|
|
30
51
|
}
|
|
31
52
|
}
|
|
32
53
|
async revoke_all() {
|
|
54
|
+
const rpc = this.#get_rpc();
|
|
55
|
+
if (!rpc) {
|
|
56
|
+
this.error = 'rpc adapter not wired';
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
33
59
|
await this.run(async () => {
|
|
34
|
-
|
|
35
|
-
if (!response.ok) {
|
|
36
|
-
throw new Error(await parse_response_error(response, 'Failed to revoke sessions'));
|
|
37
|
-
}
|
|
60
|
+
await rpc.revoke_all();
|
|
38
61
|
});
|
|
39
62
|
if (!this.error) {
|
|
40
63
|
// Current session is now revoked — next API call will 401.
|
|
@@ -6,14 +6,130 @@
|
|
|
6
6
|
import { SvelteSet } from 'svelte/reactivity';
|
|
7
7
|
import { Loadable } from './loadable.svelte.js';
|
|
8
8
|
import type { AdminAccountEntryJson } from '../auth/account_schema.js';
|
|
9
|
+
import type { AdminSessionJson } from '../auth/audit_log_schema.js';
|
|
10
|
+
import type { PermitOfferJson } from '../auth/permit_offer_schema.js';
|
|
11
|
+
/**
|
|
12
|
+
* Narrow RPC surface consumed by `AdminAccountsState`. Consumers adapt their
|
|
13
|
+
* typed RPC client (e.g. a `create_rpc_client` Proxy) to this shape — the
|
|
14
|
+
* state class stays decoupled from the client's `Result` return type so
|
|
15
|
+
* tests can inject plain-function stubs. Mirrors the `PermitOffersRpc`
|
|
16
|
+
* pattern.
|
|
17
|
+
*
|
|
18
|
+
* Every operation flows through RPC: the listing reuses `admin_account_list`,
|
|
19
|
+
* grant reuses `permit_offer_create`, revoke and retract have dedicated
|
|
20
|
+
* actions, and the session / token revoke-all mutations reuse
|
|
21
|
+
* `admin_session_revoke_all` and `admin_token_revoke_all`. Without the
|
|
22
|
+
* adapter the state class cannot fetch, grant, revoke, retract, or
|
|
23
|
+
* revoke-all sessions/tokens.
|
|
24
|
+
*/
|
|
25
|
+
export interface AdminAccountsRpc {
|
|
26
|
+
list_accounts: () => Promise<{
|
|
27
|
+
accounts: Array<AdminAccountEntryJson>;
|
|
28
|
+
grantable_roles: Array<string>;
|
|
29
|
+
}>;
|
|
30
|
+
list_sessions: () => Promise<{
|
|
31
|
+
sessions: Array<AdminSessionJson>;
|
|
32
|
+
}>;
|
|
33
|
+
grant_permit: (params: {
|
|
34
|
+
to_account_id: string;
|
|
35
|
+
role: string;
|
|
36
|
+
}) => Promise<{
|
|
37
|
+
offer: PermitOfferJson;
|
|
38
|
+
}>;
|
|
39
|
+
revoke_permit: (params: {
|
|
40
|
+
actor_id: string;
|
|
41
|
+
permit_id: string;
|
|
42
|
+
reason?: string | null;
|
|
43
|
+
}) => Promise<{
|
|
44
|
+
ok: true;
|
|
45
|
+
revoked: true;
|
|
46
|
+
}>;
|
|
47
|
+
retract_offer: (offer_id: string) => Promise<{
|
|
48
|
+
ok: true;
|
|
49
|
+
}>;
|
|
50
|
+
session_revoke_all: (params: {
|
|
51
|
+
account_id: string;
|
|
52
|
+
}) => Promise<{
|
|
53
|
+
ok: true;
|
|
54
|
+
count: number;
|
|
55
|
+
}>;
|
|
56
|
+
token_revoke_all: (params: {
|
|
57
|
+
account_id: string;
|
|
58
|
+
}) => Promise<{
|
|
59
|
+
ok: true;
|
|
60
|
+
count: number;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Svelte context carrying the reactive `AdminAccountsRpc` accessor. The
|
|
65
|
+
* provisioner (typically the admin route shell) calls `set(() => rpc)`;
|
|
66
|
+
* consumers read with `const get_rpc = admin_accounts_rpc_context.get();`
|
|
67
|
+
* and either pass the accessor straight to `AdminAccountsState`/
|
|
68
|
+
* `AdminSessionsState` or wrap it with `const rpc = $derived(get_rpc());`
|
|
69
|
+
* for direct RPC calls. Unset context falls back to `() => null` so
|
|
70
|
+
* components mounted without a provisioner surface the usual "rpc adapter
|
|
71
|
+
* not wired" path.
|
|
72
|
+
*/
|
|
73
|
+
export declare const admin_accounts_rpc_context: {
|
|
74
|
+
get: () => () => AdminAccountsRpc | null;
|
|
75
|
+
set: (value?: (() => AdminAccountsRpc | null) | undefined) => () => AdminAccountsRpc | null;
|
|
76
|
+
};
|
|
77
|
+
export interface AdminAccountsStateOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Reactive accessor for the RPC adapter; returns `null` when unwired.
|
|
80
|
+
* Matches `PermitOffersStateOptions.account_id` / `actor_id` pattern —
|
|
81
|
+
* lets the component pass a `$props()`-sourced rpc without tripping
|
|
82
|
+
* Svelte's `state_referenced_locally` warning.
|
|
83
|
+
*/
|
|
84
|
+
get_rpc?: () => AdminAccountsRpc | null;
|
|
85
|
+
}
|
|
9
86
|
export declare class AdminAccountsState extends Loadable {
|
|
87
|
+
#private;
|
|
10
88
|
accounts: Array<AdminAccountEntryJson>;
|
|
11
89
|
grantable_roles: Array<string>;
|
|
12
90
|
readonly granting_keys: SvelteSet<string>;
|
|
13
91
|
readonly revoking_ids: SvelteSet<string>;
|
|
92
|
+
readonly retracting_ids: SvelteSet<string>;
|
|
14
93
|
readonly account_count: number;
|
|
94
|
+
constructor(options?: AdminAccountsStateOptions);
|
|
95
|
+
/**
|
|
96
|
+
* True when an RPC adapter is wired. UI uses this to gate all controls
|
|
97
|
+
* — fetch, grant, revoke, retract all flow through the same adapter.
|
|
98
|
+
*/
|
|
99
|
+
get has_rpc(): boolean;
|
|
15
100
|
fetch(): Promise<void>;
|
|
16
|
-
|
|
17
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Offer the role to the recipient via the `permit_offer_create` RPC.
|
|
103
|
+
* Server returns the pending offer; the recipient must accept before
|
|
104
|
+
* the permit materializes. Returns the offer payload on success so
|
|
105
|
+
* callers can drive follow-up UX (e.g. seed `PermitOffersState.outgoing`).
|
|
106
|
+
*
|
|
107
|
+
* A re-offer from the same admin to the same `(account, role)`
|
|
108
|
+
* refreshes the existing pending row — the returned offer id is stable
|
|
109
|
+
* across those calls.
|
|
110
|
+
*
|
|
111
|
+
* No-op when the rpc adapter is absent; `error` is set to a descriptive
|
|
112
|
+
* message so the UI surfaces the misconfiguration.
|
|
113
|
+
*/
|
|
114
|
+
grant_permit(account_id: string, role: string): Promise<PermitOfferJson | undefined>;
|
|
115
|
+
/**
|
|
116
|
+
* Revoke an active permit via the `permit_revoke` RPC.
|
|
117
|
+
*
|
|
118
|
+
* `actor_id` is the natural key — permits are actor-scoped, and the
|
|
119
|
+
* admin UI reads `row.actor.id` straight from the listing, so the state
|
|
120
|
+
* class takes it directly rather than deriving it from `account_id`.
|
|
121
|
+
* The optional `reason` is stamped on `permit.revoked_reason` and
|
|
122
|
+
* surfaced on the revokee's WS notification.
|
|
123
|
+
*/
|
|
124
|
+
revoke_permit(actor_id: string, permit_id: string, reason?: string | null): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Retract a pending offer the admin issued via the `permit_offer_retract`
|
|
127
|
+
* RPC. The action handles auth, audit, and the
|
|
128
|
+
* `permit_offer_retracted` WS notification.
|
|
129
|
+
*
|
|
130
|
+
* After success, refetches the listing so `pending_offers` drops the
|
|
131
|
+
* row and the "+ {role}" button un-hides.
|
|
132
|
+
*/
|
|
133
|
+
retract_offer(offer_id: string): Promise<void>;
|
|
18
134
|
}
|
|
19
135
|
//# sourceMappingURL=admin_accounts_state.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_accounts_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_accounts_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_accounts_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_accounts_state.svelte.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,55 +4,104 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { SvelteSet } from 'svelte/reactivity';
|
|
7
|
+
import { create_context } from '@fuzdev/fuz_ui/context_helpers.js';
|
|
7
8
|
import { Loadable } from './loadable.svelte.js';
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Svelte context carrying the reactive `AdminAccountsRpc` accessor. The
|
|
11
|
+
* provisioner (typically the admin route shell) calls `set(() => rpc)`;
|
|
12
|
+
* consumers read with `const get_rpc = admin_accounts_rpc_context.get();`
|
|
13
|
+
* and either pass the accessor straight to `AdminAccountsState`/
|
|
14
|
+
* `AdminSessionsState` or wrap it with `const rpc = $derived(get_rpc());`
|
|
15
|
+
* for direct RPC calls. Unset context falls back to `() => null` so
|
|
16
|
+
* components mounted without a provisioner surface the usual "rpc adapter
|
|
17
|
+
* not wired" path.
|
|
18
|
+
*/
|
|
19
|
+
export const admin_accounts_rpc_context = create_context(() => () => null);
|
|
9
20
|
export class AdminAccountsState extends Loadable {
|
|
21
|
+
#get_rpc;
|
|
10
22
|
accounts = $state.raw([]);
|
|
11
23
|
grantable_roles = $state.raw([]);
|
|
12
24
|
granting_keys = new SvelteSet();
|
|
13
25
|
revoking_ids = new SvelteSet();
|
|
26
|
+
retracting_ids = new SvelteSet();
|
|
14
27
|
account_count = $derived(this.accounts.length);
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super();
|
|
30
|
+
this.#get_rpc = options?.get_rpc ?? (() => null);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True when an RPC adapter is wired. UI uses this to gate all controls
|
|
34
|
+
* — fetch, grant, revoke, retract all flow through the same adapter.
|
|
35
|
+
*/
|
|
36
|
+
get has_rpc() {
|
|
37
|
+
return this.#get_rpc() !== null;
|
|
38
|
+
}
|
|
15
39
|
async fetch() {
|
|
40
|
+
const rpc = this.#get_rpc();
|
|
41
|
+
if (!rpc) {
|
|
42
|
+
this.error = 'rpc adapter not wired';
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
16
45
|
await this.run(async () => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
const data = await response.json();
|
|
22
|
-
this.accounts = data.accounts ?? [];
|
|
23
|
-
this.grantable_roles = data.grantable_roles ?? [];
|
|
46
|
+
const { accounts, grantable_roles } = await rpc.list_accounts();
|
|
47
|
+
this.accounts = accounts;
|
|
48
|
+
this.grantable_roles = grantable_roles;
|
|
24
49
|
});
|
|
25
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Offer the role to the recipient via the `permit_offer_create` RPC.
|
|
53
|
+
* Server returns the pending offer; the recipient must accept before
|
|
54
|
+
* the permit materializes. Returns the offer payload on success so
|
|
55
|
+
* callers can drive follow-up UX (e.g. seed `PermitOffersState.outgoing`).
|
|
56
|
+
*
|
|
57
|
+
* A re-offer from the same admin to the same `(account, role)`
|
|
58
|
+
* refreshes the existing pending row — the returned offer id is stable
|
|
59
|
+
* across those calls.
|
|
60
|
+
*
|
|
61
|
+
* No-op when the rpc adapter is absent; `error` is set to a descriptive
|
|
62
|
+
* message so the UI surfaces the misconfiguration.
|
|
63
|
+
*/
|
|
26
64
|
async grant_permit(account_id, role) {
|
|
65
|
+
const rpc = this.#get_rpc();
|
|
66
|
+
if (!rpc) {
|
|
67
|
+
this.error = 'rpc adapter not wired';
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
27
70
|
const key = `${account_id}:${role}`;
|
|
28
71
|
this.granting_keys.add(key);
|
|
29
72
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
headers: { 'Content-Type': 'application/json' },
|
|
33
|
-
body: JSON.stringify({ role }),
|
|
34
|
-
});
|
|
35
|
-
if (!response.ok) {
|
|
36
|
-
this.error = await parse_response_error(response);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
73
|
+
const { offer } = await rpc.grant_permit({ to_account_id: account_id, role });
|
|
74
|
+
this.error = null;
|
|
39
75
|
await this.fetch();
|
|
76
|
+
return offer;
|
|
40
77
|
}
|
|
41
78
|
catch (e) {
|
|
42
79
|
this.error = e instanceof Error ? e.message : 'Failed to grant permit';
|
|
80
|
+
return undefined;
|
|
43
81
|
}
|
|
44
82
|
finally {
|
|
45
83
|
this.granting_keys.delete(key);
|
|
46
84
|
}
|
|
47
85
|
}
|
|
48
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Revoke an active permit via the `permit_revoke` RPC.
|
|
88
|
+
*
|
|
89
|
+
* `actor_id` is the natural key — permits are actor-scoped, and the
|
|
90
|
+
* admin UI reads `row.actor.id` straight from the listing, so the state
|
|
91
|
+
* class takes it directly rather than deriving it from `account_id`.
|
|
92
|
+
* The optional `reason` is stamped on `permit.revoked_reason` and
|
|
93
|
+
* surfaced on the revokee's WS notification.
|
|
94
|
+
*/
|
|
95
|
+
async revoke_permit(actor_id, permit_id, reason) {
|
|
96
|
+
const rpc = this.#get_rpc();
|
|
97
|
+
if (!rpc) {
|
|
98
|
+
this.error = 'rpc adapter not wired';
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
49
101
|
this.revoking_ids.add(permit_id);
|
|
50
102
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.error = await parse_response_error(response);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
103
|
+
await rpc.revoke_permit({ actor_id, permit_id, reason: reason ?? null });
|
|
104
|
+
this.error = null;
|
|
56
105
|
await this.fetch();
|
|
57
106
|
}
|
|
58
107
|
catch (e) {
|
|
@@ -62,4 +111,31 @@ export class AdminAccountsState extends Loadable {
|
|
|
62
111
|
this.revoking_ids.delete(permit_id);
|
|
63
112
|
}
|
|
64
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Retract a pending offer the admin issued via the `permit_offer_retract`
|
|
116
|
+
* RPC. The action handles auth, audit, and the
|
|
117
|
+
* `permit_offer_retracted` WS notification.
|
|
118
|
+
*
|
|
119
|
+
* After success, refetches the listing so `pending_offers` drops the
|
|
120
|
+
* row and the "+ {role}" button un-hides.
|
|
121
|
+
*/
|
|
122
|
+
async retract_offer(offer_id) {
|
|
123
|
+
const rpc = this.#get_rpc();
|
|
124
|
+
if (!rpc) {
|
|
125
|
+
this.error = 'rpc adapter not wired';
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.retracting_ids.add(offer_id);
|
|
129
|
+
try {
|
|
130
|
+
await rpc.retract_offer(offer_id);
|
|
131
|
+
this.error = null;
|
|
132
|
+
await this.fetch();
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
this.error = e instanceof Error ? e.message : 'Failed to retract offer';
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
this.retracting_ids.delete(offer_id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
65
141
|
}
|
|
@@ -1,17 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reactive state for admin invite management.
|
|
3
3
|
*
|
|
4
|
+
* Flows every operation through an injected `AdminInvitesRpc` adapter — the
|
|
5
|
+
* class stays decoupled from the concrete RPC client so tests can inject
|
|
6
|
+
* plain-function stubs. Mirrors `AdminAccountsRpc` / `AuditLogRpc`.
|
|
7
|
+
*
|
|
4
8
|
* @module
|
|
5
9
|
*/
|
|
6
10
|
import { SvelteSet } from 'svelte/reactivity';
|
|
7
11
|
import { Loadable } from './loadable.svelte.js';
|
|
8
|
-
import type { InviteWithUsernamesJson } from '../auth/invite_schema.js';
|
|
12
|
+
import type { InviteJson, InviteWithUsernamesJson } from '../auth/invite_schema.js';
|
|
13
|
+
/**
|
|
14
|
+
* Narrow RPC surface consumed by `AdminInvitesState`. Consumers adapt their
|
|
15
|
+
* typed RPC client to this shape. `error.data.reason` on thrown errors
|
|
16
|
+
* carries the `ERROR_INVITE_*` constant — handled by the caller when
|
|
17
|
+
* user-friendly messages are needed.
|
|
18
|
+
*/
|
|
19
|
+
export interface AdminInvitesRpc {
|
|
20
|
+
list: () => Promise<{
|
|
21
|
+
invites: Array<InviteWithUsernamesJson>;
|
|
22
|
+
}>;
|
|
23
|
+
create: (params: {
|
|
24
|
+
email?: string | null;
|
|
25
|
+
username?: string | null;
|
|
26
|
+
}) => Promise<{
|
|
27
|
+
ok: true;
|
|
28
|
+
invite: InviteJson;
|
|
29
|
+
}>;
|
|
30
|
+
delete: (params: {
|
|
31
|
+
invite_id: string;
|
|
32
|
+
}) => Promise<{
|
|
33
|
+
ok: true;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Svelte context carrying the reactive `AdminInvitesRpc` accessor. Mirrors
|
|
38
|
+
* `admin_accounts_rpc_context`. Unset context falls back to `() => null`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const admin_invites_rpc_context: {
|
|
41
|
+
get: () => () => AdminInvitesRpc | null;
|
|
42
|
+
set: (value?: (() => AdminInvitesRpc | null) | undefined) => () => AdminInvitesRpc | null;
|
|
43
|
+
};
|
|
44
|
+
export interface AdminInvitesStateOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Reactive accessor for the RPC adapter. `null` disables all operations
|
|
47
|
+
* (the state reports a descriptive error when mutations/fetches fire).
|
|
48
|
+
*/
|
|
49
|
+
get_rpc?: () => AdminInvitesRpc | null;
|
|
50
|
+
}
|
|
9
51
|
export declare class AdminInvitesState extends Loadable {
|
|
52
|
+
#private;
|
|
10
53
|
invites: Array<InviteWithUsernamesJson>;
|
|
11
54
|
creating: boolean;
|
|
12
55
|
readonly deleting_ids: SvelteSet<string>;
|
|
13
56
|
readonly invite_count: number;
|
|
14
57
|
readonly unclaimed_count: number;
|
|
58
|
+
constructor(options?: AdminInvitesStateOptions);
|
|
59
|
+
/** True when an RPC adapter is wired. All ops require it. */
|
|
60
|
+
get has_rpc(): boolean;
|
|
15
61
|
fetch(): Promise<void>;
|
|
16
62
|
create_invite(email?: string, username?: string): Promise<boolean>;
|
|
17
63
|
delete_invite(id: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_invites_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_invites_state.svelte.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"admin_invites_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/admin_invites_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAG5C,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAC,UAAU,EAAE,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AAElF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC;QAAC,OAAO,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAA;KAAC,CAAC,CAAC;IAC/D,MAAM,EAAE,CAAC,MAAM,EAAE;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,MAAM,EAAE;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC,CAAC;CAC7D;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB;qBAAwB,eAAe,GAAG,IAAI;yBAAtB,eAAe,GAAG,IAAI,wBAAtB,eAAe,GAAG,IAAI;CAEnF,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CACvC;AAED,qBAAa,iBAAkB,SAAQ,QAAQ;;IAG9C,OAAO,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAkB;IACzD,QAAQ,UAAqB;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;IAE3D,QAAQ,CAAC,YAAY,SAAiC;IACtD,QAAQ,CAAC,eAAe,SAA8D;gBAE1E,OAAO,CAAC,EAAE,wBAAwB;IAK9C,6DAA6D;IAC7D,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBlE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAgB9C"}
|