@nextblock-cms/db 0.14.5 → 0.14.6

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.
@@ -725,6 +725,45 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
725
725
  referencedColumns: ["id"];
726
726
  }];
727
727
  };
728
+ mcp_access_tokens: {
729
+ Row: {
730
+ created_at: string;
731
+ created_by: string | null;
732
+ expires_at: string | null;
733
+ id: string;
734
+ last_used_at: string | null;
735
+ name: string;
736
+ revoked_at: string | null;
737
+ scopes: string[];
738
+ token_hash: string;
739
+ token_prefix: string;
740
+ };
741
+ Insert: {
742
+ created_at?: string;
743
+ created_by?: string | null;
744
+ expires_at?: string | null;
745
+ id?: string;
746
+ last_used_at?: string | null;
747
+ name: string;
748
+ revoked_at?: string | null;
749
+ scopes?: string[];
750
+ token_hash: string;
751
+ token_prefix: string;
752
+ };
753
+ Update: {
754
+ created_at?: string;
755
+ created_by?: string | null;
756
+ expires_at?: string | null;
757
+ id?: string;
758
+ last_used_at?: string | null;
759
+ name?: string;
760
+ revoked_at?: string | null;
761
+ scopes?: string[];
762
+ token_hash?: string;
763
+ token_prefix?: string;
764
+ };
765
+ Relationships: [];
766
+ };
728
767
  media: {
729
768
  Row: {
730
769
  blur_data_url: string | null;
@@ -1523,6 +1562,48 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1523
1562
  referencedColumns: ["id"];
1524
1563
  }];
1525
1564
  };
1565
+ product_revisions: {
1566
+ Row: {
1567
+ author_id: string | null;
1568
+ content: import('./types').Json;
1569
+ created_at: string;
1570
+ id: number;
1571
+ product_id: string;
1572
+ revision_type: Database["public"]["Enums"]["revision_type"];
1573
+ version: number;
1574
+ };
1575
+ Insert: {
1576
+ author_id?: string | null;
1577
+ content: import('./types').Json;
1578
+ created_at?: string;
1579
+ id?: number;
1580
+ product_id: string;
1581
+ revision_type: Database["public"]["Enums"]["revision_type"];
1582
+ version: number;
1583
+ };
1584
+ Update: {
1585
+ author_id?: string | null;
1586
+ content?: import('./types').Json;
1587
+ created_at?: string;
1588
+ id?: number;
1589
+ product_id?: string;
1590
+ revision_type?: Database["public"]["Enums"]["revision_type"];
1591
+ version?: number;
1592
+ };
1593
+ Relationships: [{
1594
+ foreignKeyName: "product_revisions_author_id_fkey";
1595
+ columns: ["author_id"];
1596
+ isOneToOne: false;
1597
+ referencedRelation: "profiles";
1598
+ referencedColumns: ["id"];
1599
+ }, {
1600
+ foreignKeyName: "product_revisions_product_id_fkey";
1601
+ columns: ["product_id"];
1602
+ isOneToOne: false;
1603
+ referencedRelation: "products";
1604
+ referencedColumns: ["id"];
1605
+ }];
1606
+ };
1526
1607
  product_variants: {
1527
1608
  Row: {
1528
1609
  created_at: string | null;
@@ -1636,6 +1717,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1636
1717
  trial_requires_payment_method: boolean;
1637
1718
  upc: string | null;
1638
1719
  updated_at: string | null;
1720
+ version: number;
1639
1721
  };
1640
1722
  Insert: {
1641
1723
  average_rating?: number;
@@ -1674,6 +1756,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1674
1756
  trial_requires_payment_method?: boolean;
1675
1757
  upc?: string | null;
1676
1758
  updated_at?: string | null;
1759
+ version?: number;
1677
1760
  };
1678
1761
  Update: {
1679
1762
  average_rating?: number;
@@ -1712,6 +1795,7 @@ export declare const getServiceRoleSupabaseClient: () => import('@supabase/supab
1712
1795
  trial_requires_payment_method?: boolean;
1713
1796
  upc?: string | null;
1714
1797
  updated_at?: string | null;
1798
+ version?: number;
1715
1799
  };
1716
1800
  Relationships: [{
1717
1801
  foreignKeyName: "products_language_id_fkey";
@@ -749,6 +749,45 @@ export type Database = {
749
749
  }
750
750
  ];
751
751
  };
752
+ mcp_access_tokens: {
753
+ Row: {
754
+ created_at: string;
755
+ created_by: string | null;
756
+ expires_at: string | null;
757
+ id: string;
758
+ last_used_at: string | null;
759
+ name: string;
760
+ revoked_at: string | null;
761
+ scopes: string[];
762
+ token_hash: string;
763
+ token_prefix: string;
764
+ };
765
+ Insert: {
766
+ created_at?: string;
767
+ created_by?: string | null;
768
+ expires_at?: string | null;
769
+ id?: string;
770
+ last_used_at?: string | null;
771
+ name: string;
772
+ revoked_at?: string | null;
773
+ scopes?: string[];
774
+ token_hash: string;
775
+ token_prefix: string;
776
+ };
777
+ Update: {
778
+ created_at?: string;
779
+ created_by?: string | null;
780
+ expires_at?: string | null;
781
+ id?: string;
782
+ last_used_at?: string | null;
783
+ name?: string;
784
+ revoked_at?: string | null;
785
+ scopes?: string[];
786
+ token_hash?: string;
787
+ token_prefix?: string;
788
+ };
789
+ Relationships: [];
790
+ };
752
791
  media: {
753
792
  Row: {
754
793
  blur_data_url: string | null;
@@ -1586,6 +1625,51 @@ export type Database = {
1586
1625
  }
1587
1626
  ];
1588
1627
  };
1628
+ product_revisions: {
1629
+ Row: {
1630
+ author_id: string | null;
1631
+ content: Json;
1632
+ created_at: string;
1633
+ id: number;
1634
+ product_id: string;
1635
+ revision_type: Database["public"]["Enums"]["revision_type"];
1636
+ version: number;
1637
+ };
1638
+ Insert: {
1639
+ author_id?: string | null;
1640
+ content: Json;
1641
+ created_at?: string;
1642
+ id?: number;
1643
+ product_id: string;
1644
+ revision_type: Database["public"]["Enums"]["revision_type"];
1645
+ version: number;
1646
+ };
1647
+ Update: {
1648
+ author_id?: string | null;
1649
+ content?: Json;
1650
+ created_at?: string;
1651
+ id?: number;
1652
+ product_id?: string;
1653
+ revision_type?: Database["public"]["Enums"]["revision_type"];
1654
+ version?: number;
1655
+ };
1656
+ Relationships: [
1657
+ {
1658
+ foreignKeyName: "product_revisions_author_id_fkey";
1659
+ columns: ["author_id"];
1660
+ isOneToOne: false;
1661
+ referencedRelation: "profiles";
1662
+ referencedColumns: ["id"];
1663
+ },
1664
+ {
1665
+ foreignKeyName: "product_revisions_product_id_fkey";
1666
+ columns: ["product_id"];
1667
+ isOneToOne: false;
1668
+ referencedRelation: "products";
1669
+ referencedColumns: ["id"];
1670
+ }
1671
+ ];
1672
+ };
1589
1673
  product_variants: {
1590
1674
  Row: {
1591
1675
  created_at: string | null;
@@ -1702,6 +1786,7 @@ export type Database = {
1702
1786
  trial_requires_payment_method: boolean;
1703
1787
  upc: string | null;
1704
1788
  updated_at: string | null;
1789
+ version: number;
1705
1790
  };
1706
1791
  Insert: {
1707
1792
  average_rating?: number;
@@ -1740,6 +1825,7 @@ export type Database = {
1740
1825
  trial_requires_payment_method?: boolean;
1741
1826
  upc?: string | null;
1742
1827
  updated_at?: string | null;
1828
+ version?: number;
1743
1829
  };
1744
1830
  Update: {
1745
1831
  average_rating?: number;
@@ -1778,6 +1864,7 @@ export type Database = {
1778
1864
  trial_requires_payment_method?: boolean;
1779
1865
  upc?: string | null;
1780
1866
  updated_at?: string | null;
1867
+ version?: number;
1781
1868
  };
1782
1869
  Relationships: [
1783
1870
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextblock-cms/db",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "main": "index.cjs.js",
5
5
  "module": "index.es.js",
6
6
  "types": "index.d.ts",
@@ -0,0 +1,91 @@
1
+ -- Cortex AI MCP (Model Context Protocol) server access.
2
+ --
3
+ -- Adds the bearer-token store that gates /api/mcp, the endpoint that exposes the
4
+ -- Cortex AI tool registry to external MCP clients (Claude Code, Claude Desktop,
5
+ -- Cursor, VS Code). Two pieces:
6
+ --
7
+ -- 1. public.mcp_access_tokens — one row per issued token. We store ONLY the
8
+ -- SHA-256 hash of the token, never the token itself: the plaintext is shown
9
+ -- to the admin exactly once at mint time and is unrecoverable afterwards, so
10
+ -- a database leak cannot be replayed against the MCP endpoint. `token_prefix`
11
+ -- is the non-secret leading fragment kept purely so the UI can tell two tokens
12
+ -- apart in a list.
13
+ --
14
+ -- 2. cortex_ai_mcp_settings — a non-secret JSON site_settings row holding the
15
+ -- server on/off switch and the localhost-trust flag. It is added to all four
16
+ -- site_settings policies so only authenticated ADMINs can read or write it;
17
+ -- the MCP route itself reads it through the service-role client, which
18
+ -- bypasses RLS.
19
+ --
20
+ -- Forward-only. Recreates the four site_settings policies idempotently, preserving
21
+ -- every key already in each policy's sensitive array (note that
22
+ -- language_detection_settings stays anon-READABLE and so is absent from the SELECT
23
+ -- policy, exactly as migration 00000000000012 left it).
24
+
25
+ CREATE TABLE IF NOT EXISTS public.mcp_access_tokens (
26
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
27
+ name text NOT NULL,
28
+ -- Lowercase hex SHA-256 of the plaintext token. Unique so a lookup is a single
29
+ -- indexed equality probe and duplicate mints are impossible.
30
+ token_hash text NOT NULL UNIQUE,
31
+ -- Non-secret display fragment, e.g. "nbmcp_a1b2c3d4". Never enough to authenticate.
32
+ token_prefix text NOT NULL,
33
+ -- 'read' grants the read-only tools; 'write' additionally grants the mutating ones.
34
+ scopes text[] NOT NULL DEFAULT ARRAY['read', 'write']::text[],
35
+ created_by uuid REFERENCES auth.users (id) ON DELETE SET NULL,
36
+ created_at timestamptz NOT NULL DEFAULT now(),
37
+ last_used_at timestamptz,
38
+ expires_at timestamptz,
39
+ revoked_at timestamptz
40
+ );
41
+
42
+ COMMENT ON TABLE public.mcp_access_tokens IS
43
+ 'Bearer tokens for the Cortex AI MCP server at /api/mcp. Stores SHA-256 hashes only; plaintext is displayed once at mint time.';
44
+
45
+ CREATE INDEX IF NOT EXISTS mcp_access_tokens_token_hash_idx
46
+ ON public.mcp_access_tokens (token_hash);
47
+
48
+ -- Orders the admin token list newest-first without a sort.
49
+ CREATE INDEX IF NOT EXISTS mcp_access_tokens_created_at_idx
50
+ ON public.mcp_access_tokens (created_at DESC);
51
+
52
+ ALTER TABLE public.mcp_access_tokens ENABLE ROW LEVEL SECURITY;
53
+
54
+ -- Tokens are credentials: admin-only, with no anon or WRITER access at all. The
55
+ -- MCP route verifies them with the service-role client, which bypasses RLS.
56
+ DROP POLICY IF EXISTS mcp_access_tokens_admin_select ON public.mcp_access_tokens;
57
+ CREATE POLICY mcp_access_tokens_admin_select ON public.mcp_access_tokens
58
+ FOR SELECT TO authenticated
59
+ USING ((SELECT public.get_current_user_role()) = 'ADMIN'::public.user_role);
60
+
61
+ DROP POLICY IF EXISTS mcp_access_tokens_admin_insert ON public.mcp_access_tokens;
62
+ CREATE POLICY mcp_access_tokens_admin_insert ON public.mcp_access_tokens
63
+ FOR INSERT TO authenticated
64
+ WITH CHECK ((SELECT public.get_current_user_role()) = 'ADMIN'::public.user_role);
65
+
66
+ DROP POLICY IF EXISTS mcp_access_tokens_admin_update ON public.mcp_access_tokens;
67
+ CREATE POLICY mcp_access_tokens_admin_update ON public.mcp_access_tokens
68
+ FOR UPDATE TO authenticated
69
+ USING ((SELECT public.get_current_user_role()) = 'ADMIN'::public.user_role)
70
+ WITH CHECK ((SELECT public.get_current_user_role()) = 'ADMIN'::public.user_role);
71
+
72
+ DROP POLICY IF EXISTS mcp_access_tokens_admin_delete ON public.mcp_access_tokens;
73
+ CREATE POLICY mcp_access_tokens_admin_delete ON public.mcp_access_tokens
74
+ FOR DELETE TO authenticated
75
+ USING ((SELECT public.get_current_user_role()) = 'ADMIN'::public.user_role);
76
+
77
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.mcp_access_tokens TO authenticated;
78
+ GRANT ALL ON public.mcp_access_tokens TO service_role;
79
+
80
+ -- Add cortex_ai_mcp_settings to the admin-only site_settings group (all four policies).
81
+ DROP POLICY IF EXISTS site_settings_read_policy ON public.site_settings;
82
+ CREATE POLICY site_settings_read_policy ON public.site_settings FOR SELECT USING (((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT auth.role() AS role) = 'authenticated'::text) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));
83
+
84
+ DROP POLICY IF EXISTS site_settings_insert_policy ON public.site_settings;
85
+ CREATE POLICY site_settings_insert_policy ON public.site_settings FOR INSERT TO authenticated WITH CHECK ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));
86
+
87
+ DROP POLICY IF EXISTS site_settings_update_policy ON public.site_settings;
88
+ CREATE POLICY site_settings_update_policy ON public.site_settings FOR UPDATE TO authenticated USING ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role)))) WITH CHECK ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));
89
+
90
+ DROP POLICY IF EXISTS site_settings_delete_policy ON public.site_settings;
91
+ CREATE POLICY site_settings_delete_policy ON public.site_settings FOR DELETE TO authenticated USING ((((key <> ALL (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = ANY (ARRAY['ADMIN'::public.user_role, 'WRITER'::public.user_role]))) OR ((key = ANY (ARRAY['cortex_ai_openrouter_api_key'::text, 'bot_protection_secret'::text, 'email_secret'::text, 'payment_secret'::text, 'language_detection_settings'::text, 'cortex_ai_pexels_api_key'::text, 'cortex_ai_unsplash_access_key'::text, 'cortex_ai_mcp_settings'::text])) AND (( SELECT public.get_current_user_role() AS get_current_user_role) = 'ADMIN'::public.user_role))));