@oktis-works/database 0.2.1 → 0.3.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.
@@ -467,11 +467,17 @@ CREATE TABLE IF NOT EXISTS content_types (
467
467
  schema JSONB NOT NULL,
468
468
  plural_label VARCHAR(255) NOT NULL,
469
469
  singular_label VARCHAR(255) NOT NULL,
470
- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
470
+ default_fields JSONB NOT NULL DEFAULT '["title","slug","content","excerpt","featured_image","seo_title","seo_description","status","author"]'::jsonb,
471
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
472
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
471
473
  );
472
474
 
473
475
  CREATE INDEX IF NOT EXISTS idx_content_types_slug ON content_types(slug);
474
476
 
477
+ -- Add new columns to existing content_types table (idempotent)
478
+ ALTER TABLE content_types ADD COLUMN IF NOT EXISTS default_fields JSONB NOT NULL DEFAULT '["title","slug","content","excerpt","featured_image","seo_title","seo_description","status","author"]'::jsonb;
479
+ ALTER TABLE content_types ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
480
+
475
481
  -- ============================================================
476
482
  -- Webhooks
477
483
  -- ============================================================
@@ -527,8 +533,15 @@ CREATE TABLE IF NOT EXISTS taxonomies (
527
533
  CREATE INDEX IF NOT EXISTS idx_taxonomies_tenant ON taxonomies(tenant_id);
528
534
  CREATE INDEX IF NOT EXISTS idx_taxonomies_slug ON taxonomies(slug);
529
535
  ALTER TABLE taxonomies ENABLE ROW LEVEL SECURITY;
530
- CREATE POLICY tenant_isolation_taxonomies ON taxonomies
531
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
536
+ DO $$
537
+ BEGIN
538
+ IF NOT EXISTS (
539
+ SELECT 1 FROM pg_policies
540
+ WHERE policyname = 'tenant_isolation_taxonomies' AND tablename = 'taxonomies'
541
+ ) THEN
542
+ CREATE POLICY tenant_isolation_taxonomies ON taxonomies USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
543
+ END IF;
544
+ END $$;
532
545
 
533
546
  -- ============================================================
534
547
  -- Field Groups (SDD ENTI-026)
@@ -550,8 +563,15 @@ CREATE TABLE IF NOT EXISTS field_groups (
550
563
  CREATE INDEX IF NOT EXISTS idx_field_groups_tenant ON field_groups(tenant_id);
551
564
  CREATE INDEX IF NOT EXISTS idx_field_groups_active ON field_groups(active);
552
565
  ALTER TABLE field_groups ENABLE ROW LEVEL SECURITY;
553
- CREATE POLICY tenant_isolation_field_groups ON field_groups
554
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
566
+ DO $$
567
+ BEGIN
568
+ IF NOT EXISTS (
569
+ SELECT 1 FROM pg_policies
570
+ WHERE policyname = 'tenant_isolation_field_groups' AND tablename = 'field_groups'
571
+ ) THEN
572
+ CREATE POLICY tenant_isolation_field_groups ON field_groups USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
573
+ END IF;
574
+ END $$;
555
575
 
556
576
  -- ============================================================
557
577
  -- Field Definitions (SDD ENTI-027)
@@ -569,19 +589,68 @@ CREATE TABLE IF NOT EXISTS field_definitions (
569
589
  required BOOLEAN NOT NULL DEFAULT false,
570
590
  config JSONB NOT NULL DEFAULT '{}'::jsonb,
571
591
  sort_order INTEGER NOT NULL DEFAULT 0,
592
+ is_core_field BOOLEAN NOT NULL DEFAULT false,
593
+ is_locked BOOLEAN NOT NULL DEFAULT false,
594
+ core_field_key VARCHAR(100),
572
595
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
573
596
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
574
597
  );
575
598
 
599
+ -- Add new columns to existing field_definitions table (idempotent)
600
+ ALTER TABLE field_definitions ADD COLUMN IF NOT EXISTS is_core_field BOOLEAN NOT NULL DEFAULT false;
601
+ ALTER TABLE field_definitions ADD COLUMN IF NOT EXISTS is_locked BOOLEAN NOT NULL DEFAULT false;
602
+ ALTER TABLE field_definitions ADD COLUMN IF NOT EXISTS core_field_key VARCHAR(100);
603
+
576
604
  CREATE INDEX IF NOT EXISTS idx_field_definitions_group ON field_definitions(group_id);
577
605
  CREATE INDEX IF NOT EXISTS idx_field_definitions_parent ON field_definitions(parent_field_id) WHERE parent_field_id IS NOT NULL;
606
+ CREATE INDEX IF NOT EXISTS idx_field_definitions_core ON field_definitions(is_core_field) WHERE is_core_field = true;
607
+ CREATE INDEX IF NOT EXISTS idx_field_definitions_locked ON field_definitions(is_locked) WHERE is_locked = true;
608
+ CREATE INDEX IF NOT EXISTS idx_field_definitions_core_key ON field_definitions(core_field_key) WHERE core_field_key IS NOT NULL;
578
609
  ALTER TABLE field_definitions ENABLE ROW LEVEL SECURITY;
579
- CREATE POLICY tenant_isolation_field_definitions ON field_definitions
580
- USING (EXISTS (
610
+ DO $$
611
+ BEGIN
612
+ IF NOT EXISTS (
613
+ SELECT 1 FROM pg_policies
614
+ WHERE policyname = 'tenant_isolation_field_definitions' AND tablename = 'field_definitions'
615
+ ) THEN
616
+ CREATE POLICY tenant_isolation_field_definitions ON field_definitions USING (EXISTS (
581
617
  SELECT 1 FROM field_groups fg
582
618
  WHERE fg.id = field_definitions.group_id
583
619
  AND (fg.tenant_id = current_setting('app.current_tenant_id', true)::uuid OR fg.tenant_id IS NULL)
584
620
  ));
621
+ END IF;
622
+ END $$;
623
+
624
+ -- ============================================================
625
+ -- Field Values (valores isolados por entidade)
626
+ -- ============================================================
627
+ CREATE TABLE IF NOT EXISTS field_values (
628
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
629
+ field_definition_id UUID NOT NULL REFERENCES field_definitions(id) ON DELETE CASCADE,
630
+ entity_type VARCHAR(50) NOT NULL CHECK (entity_type IN ('content', 'taxonomy_term')),
631
+ entity_id UUID NOT NULL,
632
+ value JSONB NOT NULL DEFAULT '{}'::jsonb,
633
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
634
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
635
+ UNIQUE(field_definition_id, entity_type, entity_id)
636
+ );
637
+
638
+ CREATE INDEX IF NOT EXISTS idx_field_values_entity ON field_values(entity_type, entity_id);
639
+ CREATE INDEX IF NOT EXISTS idx_field_values_field ON field_values(field_definition_id);
640
+ CREATE INDEX IF NOT EXISTS idx_field_values_updated ON field_values(updated_at DESC);
641
+
642
+ CREATE OR REPLACE FUNCTION update_field_values_timestamp()
643
+ RETURNS TRIGGER AS $$
644
+ BEGIN
645
+ NEW.updated_at = NOW();
646
+ RETURN NEW;
647
+ END;
648
+ $$ LANGUAGE plpgsql;
649
+
650
+ DROP TRIGGER IF EXISTS trg_field_values_updated ON field_values;
651
+ CREATE TRIGGER trg_field_values_updated
652
+ BEFORE UPDATE ON field_values
653
+ FOR EACH ROW EXECUTE FUNCTION update_field_values_timestamp();
585
654
 
586
655
  -- ============================================================
587
656
  -- Flexible Layouts (SDD ENTI-028)
@@ -650,44 +719,142 @@ ALTER TABLE sessions ENABLE ROW LEVEL SECURITY;
650
719
  ALTER TABLE webhooks ENABLE ROW LEVEL SECURITY;
651
720
 
652
721
  -- RLS Policies using app.current_tenant_id session variable
653
- CREATE POLICY tenant_isolation_content ON content
654
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
655
-
656
- CREATE POLICY tenant_isolation_media ON media
657
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
658
-
659
- CREATE POLICY tenant_isolation_categories ON categories
660
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
661
-
662
- CREATE POLICY tenant_isolation_tags ON tags
663
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
664
-
665
- CREATE POLICY tenant_isolation_menus ON menus
666
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
667
-
668
- CREATE POLICY tenant_isolation_settings ON settings
669
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
670
-
671
- CREATE POLICY tenant_isolation_plugins ON plugins
672
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
673
-
674
- CREATE POLICY tenant_isolation_themes ON themes
675
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
676
-
677
- CREATE POLICY tenant_isolation_builds ON builds
678
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
679
-
680
- CREATE POLICY tenant_isolation_deployments ON deployments
681
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
682
-
683
- CREATE POLICY tenant_isolation_events ON events
684
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
685
-
686
- CREATE POLICY tenant_isolation_audit_logs ON audit_logs
687
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
688
-
689
- CREATE POLICY tenant_isolation_sessions ON sessions
690
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
691
-
692
- CREATE POLICY tenant_isolation_webhooks ON webhooks
693
- USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
722
+ DO $$
723
+ BEGIN
724
+ IF NOT EXISTS (
725
+ SELECT 1 FROM pg_policies
726
+ WHERE policyname = 'tenant_isolation_content' AND tablename = 'content'
727
+ ) THEN
728
+ CREATE POLICY tenant_isolation_content ON content USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
729
+ END IF;
730
+ END $$;
731
+
732
+ DO $$
733
+ BEGIN
734
+ IF NOT EXISTS (
735
+ SELECT 1 FROM pg_policies
736
+ WHERE policyname = 'tenant_isolation_media' AND tablename = 'media'
737
+ ) THEN
738
+ CREATE POLICY tenant_isolation_media ON media USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
739
+ END IF;
740
+ END $$;
741
+
742
+ DO $$
743
+ BEGIN
744
+ IF NOT EXISTS (
745
+ SELECT 1 FROM pg_policies
746
+ WHERE policyname = 'tenant_isolation_categories' AND tablename = 'categories'
747
+ ) THEN
748
+ CREATE POLICY tenant_isolation_categories ON categories USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
749
+ END IF;
750
+ END $$;
751
+
752
+ DO $$
753
+ BEGIN
754
+ IF NOT EXISTS (
755
+ SELECT 1 FROM pg_policies
756
+ WHERE policyname = 'tenant_isolation_tags' AND tablename = 'tags'
757
+ ) THEN
758
+ CREATE POLICY tenant_isolation_tags ON tags USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
759
+ END IF;
760
+ END $$;
761
+
762
+ DO $$
763
+ BEGIN
764
+ IF NOT EXISTS (
765
+ SELECT 1 FROM pg_policies
766
+ WHERE policyname = 'tenant_isolation_menus' AND tablename = 'menus'
767
+ ) THEN
768
+ CREATE POLICY tenant_isolation_menus ON menus USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
769
+ END IF;
770
+ END $$;
771
+
772
+ DO $$
773
+ BEGIN
774
+ IF NOT EXISTS (
775
+ SELECT 1 FROM pg_policies
776
+ WHERE policyname = 'tenant_isolation_settings' AND tablename = 'settings'
777
+ ) THEN
778
+ CREATE POLICY tenant_isolation_settings ON settings USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
779
+ END IF;
780
+ END $$;
781
+
782
+ DO $$
783
+ BEGIN
784
+ IF NOT EXISTS (
785
+ SELECT 1 FROM pg_policies
786
+ WHERE policyname = 'tenant_isolation_plugins' AND tablename = 'plugins'
787
+ ) THEN
788
+ CREATE POLICY tenant_isolation_plugins ON plugins USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
789
+ END IF;
790
+ END $$;
791
+
792
+ DO $$
793
+ BEGIN
794
+ IF NOT EXISTS (
795
+ SELECT 1 FROM pg_policies
796
+ WHERE policyname = 'tenant_isolation_themes' AND tablename = 'themes'
797
+ ) THEN
798
+ CREATE POLICY tenant_isolation_themes ON themes USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
799
+ END IF;
800
+ END $$;
801
+
802
+ DO $$
803
+ BEGIN
804
+ IF NOT EXISTS (
805
+ SELECT 1 FROM pg_policies
806
+ WHERE policyname = 'tenant_isolation_builds' AND tablename = 'builds'
807
+ ) THEN
808
+ CREATE POLICY tenant_isolation_builds ON builds USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
809
+ END IF;
810
+ END $$;
811
+
812
+ DO $$
813
+ BEGIN
814
+ IF NOT EXISTS (
815
+ SELECT 1 FROM pg_policies
816
+ WHERE policyname = 'tenant_isolation_deployments' AND tablename = 'deployments'
817
+ ) THEN
818
+ CREATE POLICY tenant_isolation_deployments ON deployments USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
819
+ END IF;
820
+ END $$;
821
+
822
+ DO $$
823
+ BEGIN
824
+ IF NOT EXISTS (
825
+ SELECT 1 FROM pg_policies
826
+ WHERE policyname = 'tenant_isolation_events' AND tablename = 'events'
827
+ ) THEN
828
+ CREATE POLICY tenant_isolation_events ON events USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
829
+ END IF;
830
+ END $$;
831
+
832
+ DO $$
833
+ BEGIN
834
+ IF NOT EXISTS (
835
+ SELECT 1 FROM pg_policies
836
+ WHERE policyname = 'tenant_isolation_audit_logs' AND tablename = 'audit_logs'
837
+ ) THEN
838
+ CREATE POLICY tenant_isolation_audit_logs ON audit_logs USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
839
+ END IF;
840
+ END $$;
841
+
842
+ DO $$
843
+ BEGIN
844
+ IF NOT EXISTS (
845
+ SELECT 1 FROM pg_policies
846
+ WHERE policyname = 'tenant_isolation_sessions' AND tablename = 'sessions'
847
+ ) THEN
848
+ CREATE POLICY tenant_isolation_sessions ON sessions USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
849
+ END IF;
850
+ END $$;
851
+
852
+ DO $$
853
+ BEGIN
854
+ IF NOT EXISTS (
855
+ SELECT 1 FROM pg_policies
856
+ WHERE policyname = 'tenant_isolation_webhooks' AND tablename = 'webhooks'
857
+ ) THEN
858
+ CREATE POLICY tenant_isolation_webhooks ON webhooks USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
859
+ END IF;
860
+ END $$;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oktis-works/database",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "db:rollback": "node --loader ts-node/esm src/migrations/runner.ts rollback"
20
20
  },
21
21
  "dependencies": {
22
- "@oktis-works/types": "^0.2.1",
23
- "@oktis-works/config": "^0.2.1",
22
+ "@oktis-works/types": "^0.3.1",
23
+ "@oktis-works/config": "^0.3.1",
24
24
  "postgres": "3.4.0"
25
25
  },
26
26
  "devDependencies": {