@fayz-ai/db 0.24.0 → 0.24.1

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.
@@ -0,0 +1,25 @@
1
+ -- 206: the account's access layers get one screen (Configurações › Acesso):
2
+ -- QR code access (keys from 205), 2FA, invitations and the inactivity timeout.
3
+ -- The last three were written by the old Security screen as an untyped blob in
4
+ -- tenants.settings.security; they become typed keys, and whatever an account
5
+ -- already chose there is carried over. Nothing reads them yet (marked "Em
6
+ -- breve" on screen). Additive and idempotent: an existing access.* value wins.
7
+
8
+ SELECT app.seed_setting_keys($keys$[
9
+ { "key": "access.require_2fa", "type": "boolean", "default": false, "scope": "tenant", "reader": "core",
10
+ "description": "Every login confirms with an authenticator code. Saved, not enforced yet." },
11
+ { "key": "access.invite_only", "type": "boolean", "default": true, "scope": "tenant", "reader": "core",
12
+ "description": "Only invited people join the account. Saved, not enforced yet." },
13
+ { "key": "access.session_timeout_minutes", "type": "number", "default": 0, "scope": "tenant", "reader": "core",
14
+ "description": "Sign out after this many idle minutes; 0 is no limit. Saved, not enforced yet." }
15
+ ]$keys$::jsonb);
16
+
17
+ UPDATE public.tenants t
18
+ SET settings = jsonb_set(coalesce(t.settings, '{}'::jsonb), '{access}',
19
+ coalesce(t.settings -> 'access', '{}'::jsonb)
20
+ || jsonb_strip_nulls(jsonb_build_object(
21
+ 'require_2fa', t.settings -> 'security' -> 'require2FA',
22
+ 'invite_only', t.settings -> 'security' -> 'inviteOnly',
23
+ 'session_timeout_minutes', t.settings -> 'security' -> 'sessionTimeoutMinutes'))
24
+ || coalesce(t.settings -> 'access', '{}'::jsonb))
25
+ WHERE jsonb_typeof(t.settings -> 'security') = 'object';
@@ -0,0 +1,65 @@
1
+ -- 207: a line is read and changed with the permission of the document it
2
+ -- belongs to. public.items holds order, invoice and fulfillment lines; the
3
+ -- insert policy already asked financial.create for a bill's line, but the
4
+ -- scaffold generator rewrote select/update/delete from the table's registry
5
+ -- plugin (orders), so anyone without orders.read stopped seeing the lines of
6
+ -- their own bills — a bill showed its total and no items. The generator now
7
+ -- renders a parent-aware check for public.items, and the policies are rebuilt.
8
+ -- Additive and idempotent.
9
+
10
+ CREATE OR REPLACE FUNCTION app.scaffold_line_perm_sql(p_action text)
11
+ RETURNS text
12
+ LANGUAGE sql IMMUTABLE
13
+ SET search_path TO ''
14
+ AS $$
15
+ SELECT format('(select app.has_permission(CASE parent_kind WHEN %L THEN %L WHEN %L THEN %L ELSE %L END, unit_id))',
16
+ 'invoice', 'financial.' || p_action, 'fulfillment', 'inventory.' || p_action, 'orders.' || p_action);
17
+ $$;
18
+ REVOKE ALL ON FUNCTION app.scaffold_line_perm_sql(text) FROM PUBLIC;
19
+ GRANT ALL ON FUNCTION app.scaffold_line_perm_sql(text) TO service_role;
20
+
21
+ CREATE OR REPLACE FUNCTION app.scaffold_refresh_policies(p_table text) RETURNS void
22
+ LANGUAGE plpgsql SECURITY DEFINER
23
+ SET search_path TO ''
24
+ AS $$
25
+ DECLARE
26
+ v_schema text := split_part(replace(p_table, '"', ''), '.', 1);
27
+ v_name text := split_part(replace(p_table, '"', ''), '.', 2);
28
+ v_q text := format('%I.%I', split_part(replace(p_table, '"', ''), '.', 1), split_part(replace(p_table, '"', ''), '.', 2));
29
+ r app.scaffold_registry%ROWTYPE;
30
+ v_scope text;
31
+ v_class text;
32
+ v_items boolean;
33
+ BEGIN
34
+ SELECT * INTO r FROM app.scaffold_registry WHERE table_schema = v_schema AND table_name = v_name;
35
+ IF NOT FOUND THEN
36
+ RAISE EXCEPTION 'scaffold_refresh_policies: %.% is not in the scaffold registry', v_schema, v_name USING ERRCODE = 'P0002';
37
+ END IF;
38
+
39
+ -- The `unit` rendering drops the `unit_id IS NULL OR` branch, which is only
40
+ -- sound when the column really is NOT NULL. Declaring a table `unit` while its
41
+ -- writers still produce NULLs is exactly the state this file leaves most tables
42
+ -- in — so the optimisation follows the CONSTRAINT, never the label. Getting
43
+ -- this wrong hid every unit-less financial movement and took
44
+ -- v_financial_customer_credit to 0.00.
45
+ SELECT CASE WHEN c.is_nullable = 'NO' THEN 'unit' ELSE NULL END INTO v_class
46
+ FROM information_schema.columns c
47
+ WHERE c.table_schema = v_schema AND c.table_name = v_name AND c.column_name = 'unit_id';
48
+ v_scope := app.scaffold_scope_sql(r.resource_type, r.plugin, v_class);
49
+ v_items := v_schema = 'public' AND v_name = 'items';
50
+
51
+ EXECUTE format('DROP POLICY IF EXISTS %I ON %s', v_name || '_authz_select', v_q);
52
+ EXECUTE format('CREATE POLICY %I ON %s FOR SELECT TO authenticated USING (%s AND %s)',
53
+ v_name || '_authz_select', v_q, v_scope, CASE WHEN v_items THEN app.scaffold_line_perm_sql('read') ELSE app.scaffold_perm_sql(r.plugin || '.read', v_class) END);
54
+ EXECUTE format('DROP POLICY IF EXISTS %I ON %s', v_name || '_authz_update', v_q);
55
+ EXECUTE format('CREATE POLICY %I ON %s FOR UPDATE TO authenticated USING (%s AND %s) WITH CHECK (tenant_id = (select app.current_tenant_id()) AND (unit_id IS NULL OR (select app.has_unit(unit_id))) AND %s)',
56
+ v_name || '_authz_update', v_q, v_scope, CASE WHEN v_items THEN app.scaffold_line_perm_sql('edit') ELSE app.scaffold_perm_sql(r.plugin || '.edit', v_class) END,
57
+ CASE WHEN v_items THEN app.scaffold_line_perm_sql('edit') ELSE format('(select app.has_permission(%L, unit_id))', r.plugin || '.edit') END);
58
+ EXECUTE format('DROP POLICY IF EXISTS %I ON %s', v_name || '_authz_delete', v_q);
59
+ EXECUTE format('CREATE POLICY %I ON %s FOR DELETE TO authenticated USING (%s AND %s)',
60
+ v_name || '_authz_delete', v_q, v_scope, CASE WHEN v_items THEN app.scaffold_line_perm_sql('delete') ELSE app.scaffold_perm_sql(r.plugin || '.delete', v_class) END);
61
+ END $$;
62
+
63
+
64
+ SELECT app.scaffold_refresh_policies('public.items')
65
+ WHERE EXISTS (SELECT 1 FROM app.scaffold_registry WHERE table_schema = 'public' AND table_name = 'items');
@@ -0,0 +1,18 @@
1
+ -- 208: the top-bar switches "Delivery" and "Cardápio QR" say whether the house
2
+ -- is taking those orders right now, and each unit answers for itself. Until now
3
+ -- the switches held their state in the screen only and forgot it on reload.
4
+ -- `delivery.enabled` becomes unit-scoped (house default + a unit's own value; no
5
+ -- unit holds one yet), and the QR menu gets its key. Additive and idempotent.
6
+
7
+ SELECT app.seed_setting_keys($keys$[
8
+ {
9
+ "key": "delivery.enabled", "type": "boolean", "default": true, "scope": "unit",
10
+ "reader": "plugin:orders",
11
+ "description": "Se a casa entrega. Desligado, o quadro de despacho segue existindo para o histórico e nenhum canal novo nasce como entrega."
12
+ },
13
+ {
14
+ "key": "qr_menu.enabled", "type": "boolean", "default": true, "scope": "unit",
15
+ "reader": "plugin:menu",
16
+ "description": "Se o cardápio digital (QR) está recebendo pedidos agora. Desligado, a mesa vê o cardápio e não pede."
17
+ }
18
+ ]$keys$::jsonb);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.24.0",
6
+ "version": "0.24.1",
7
7
  "description": "Fayz SDK database layer — Drizzle schema primitives, spine references, and migration helpers shared across plugins.",
8
8
  "type": "module",
9
9
  "sideEffects": false,