@fayz-ai/db 0.24.0 → 0.24.2
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/index.cjs +788 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/migrations/198_permission_holes_close.sql +136 -0
- package/migrations/199_field_rules_speak_the_crud_entity.sql +82 -0
- package/migrations/200_team_members_have_a_name.sql +20 -0
- package/migrations/201_paid_features_are_facets.sql +39 -0
- package/migrations/202_a_unit_switches_off_a_module.sql +173 -0
- package/migrations/203_delivery_is_a_module.sql +31 -0
- package/migrations/204_a_module_off_in_a_unit_does_not_write_there.sql +95 -0
- package/migrations/205_v1_settings_screens_save.sql +833 -0
- package/migrations/206_the_account_access_has_one_screen.sql +25 -0
- package/migrations/207_a_line_answers_to_its_document.sql +65 -0
- package/migrations/208_a_service_opens_and_closes_per_unit.sql +18 -0
- package/migrations/209_the_store_gets_its_rail_group.sql +37 -0
- package/package.json +1 -1
|
@@ -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);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 209 — the store gets a "Loja" group in the ChefControl and GoControl rails
|
|
3
|
+
--
|
|
4
|
+
-- A tenant's rail is its FIRST app's preset (app_config orders trades first).
|
|
5
|
+
-- Artorius runs ChefControl + StoreControl + GoControl, so it draws the
|
|
6
|
+
-- ChefControl rail, which has no group for the shop's screens: Descontos and
|
|
7
|
+
-- Conteúdo fell loose at the bottom, and nothing said "Loja". StoreControl's
|
|
8
|
+
-- own rail has the group; the trades that also sell online did not.
|
|
9
|
+
--
|
|
10
|
+
-- The group only collects routes that exist: a tenant without the shop sees
|
|
11
|
+
-- no empty heading. /products and /orders stay where these rails already put
|
|
12
|
+
-- them (Estoque, Operação).
|
|
13
|
+
--
|
|
14
|
+
-- Placed right after the Estoque group. Idempotent: skipped where a "Loja"
|
|
15
|
+
-- group is already present.
|
|
16
|
+
-- ============================================================================
|
|
17
|
+
|
|
18
|
+
UPDATE app.apps a
|
|
19
|
+
SET preset = jsonb_set(
|
|
20
|
+
a.preset,
|
|
21
|
+
'{rail}',
|
|
22
|
+
jsonb_insert(
|
|
23
|
+
a.preset -> 'rail',
|
|
24
|
+
ARRAY[(
|
|
25
|
+
SELECT (e.ord)::text
|
|
26
|
+
FROM jsonb_array_elements(a.preset -> 'rail') WITH ORDINALITY AS e(item, ord)
|
|
27
|
+
WHERE e.item ->> 'label' = 'Estoque'
|
|
28
|
+
LIMIT 1
|
|
29
|
+
)],
|
|
30
|
+
'{"icon":"ShoppingBag","label":"Loja","under":["/discounts","/content","/shop"]}'::jsonb
|
|
31
|
+
)
|
|
32
|
+
),
|
|
33
|
+
updated_at = now()
|
|
34
|
+
WHERE a.id IN ('resto', 'full')
|
|
35
|
+
AND jsonb_typeof(a.preset -> 'rail') = 'array'
|
|
36
|
+
AND EXISTS (SELECT 1 FROM jsonb_array_elements(a.preset -> 'rail') e WHERE e ->> 'label' = 'Estoque')
|
|
37
|
+
AND NOT EXISTS (SELECT 1 FROM jsonb_array_elements(a.preset -> 'rail') e WHERE e ->> 'label' = 'Loja');
|
package/package.json
CHANGED