diff --git a/libervia/backend/memory/migration/versions/007439b3b0ce_add_pep_to_pubsubnode.py b/libervia/backend/memory/migration/versions/007439b3b0ce_add_pep_to_pubsubnode.py new file mode 100644 index 00000000..531a1dcf --- /dev/null +++ b/libervia/backend/memory/migration/versions/007439b3b0ce_add_pep_to_pubsubnode.py @@ -0,0 +1,42 @@ +"""add "pep" to PubsubNode + +Revision ID: 007439b3b0ce +Revises: f9ce59cfc13f +Create Date: 2026-04-12 00:46:56.674238 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "007439b3b0ce" +down_revision = "f9ce59cfc13f" +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table("pubsub_nodes", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "pep", + sa.Boolean(create_constraint=True), + server_default=sa.text("0"), + nullable=False, + ) + ) + batch_op.drop_constraint(batch_op.f("uq_pubsub_nodes_profile_id"), type_="unique") + batch_op.create_unique_constraint( + batch_op.f("uq_pubsub_nodes_profile_id"), + ["profile_id", "service", "name", "pep"], + ) + + +def downgrade(): + with op.batch_alter_table("pubsub_nodes", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_pubsub_nodes_profile_id"), type_="unique") + batch_op.create_unique_constraint( + batch_op.f("uq_pubsub_nodes_profile_id"), ["profile_id", "service", "name"] + ) + batch_op.drop_column("pep") diff --git a/libervia/backend/memory/sqla.py b/libervia/backend/memory/sqla.py index 6ee46f59..643a430a 100644 --- a/libervia/backend/memory/sqla.py +++ b/libervia/backend/memory/sqla.py @@ -1188,6 +1188,7 @@ class Storage: service: jid.JID, name: str | None = None, node_id: int | None = None, + pep: bool | None = None, with_items: bool = False, with_subscriptions: bool = False, with_affiliations: bool = False, @@ -1203,6 +1204,7 @@ class Storage: Can't be used with ``node_id`` @param node_id: node's ID Can't be used with ``node_id` or ``create`` + @param pep: if set, filter on PEP/non-PEP nodes @param with_items: retrieve items in the same query @param with_subscriptions: retrieve subscriptions in the same query @param with_affiliations: retrieve affiliations in the same query @@ -1224,11 +1226,14 @@ class Storage: # When querying by ID, we only need it. stmt = select(PubsubNode).filter_by(id=node_id) else: - stmt = select(PubsubNode).filter_by( - service=service, - profile_id=self.profiles[client.profile], - name=name, - ) + filters = { + "service": service, + "profile_id": self.profiles[client.profile], + "name": name, + } + if pep is not None: + filters["pep"] = pep + stmt = select(PubsubNode).filter_by(**filters) if with_items: stmt = stmt.options(joinedload(PubsubNode.items)) if with_subscriptions: @@ -1444,11 +1449,37 @@ class Storage: parent_node: PubsubNode | None = None, linked_node: PubsubNode | None = None, max_items: int = 0, + pep: bool = False, ) -> PubsubNode: + """Create a new pubsub node. + + @param client: The client creating the node. + @param service: The pubsub service JID. + For PEP nodes (pep=True), this should be the bare JID of the PEP owner. + @param name: The node name. + @param access_model: The access model for the node. + @param publish_model: The publish model for the node. + @param analyser: The analyser for the node. + @param type_: The node type. + @param subtype: The node subtype. + @param subscribed: Whether the creator is subscribed. + @param extra: Extra node metadata. + @param items: Initial items for the node. + @param affiliations: Initial affiliations for the node. + @param subscriptions: Initial subscriptions for the node. + @param parent_node: Parent node (for XEP-0496). + @param linked_node: Linked node (for XEP-0496). + @param max_items: Maximum number of items to persist (0 = unlimited). + @param pep: If True, this is a PEP-scoped node. + For PEP nodes, the service JID should be the bare JID of the PEP owner. + @return: The created node. + @raise ConflictError: If a node with the same name already exists. + """ node = PubsubNode( profile_id=self.profiles[client.profile], service=service, name=name, + pep=pep, access_model=access_model, publish_model=publish_model, subscribed=subscribed, diff --git a/libervia/backend/memory/sqla_mapping.py b/libervia/backend/memory/sqla_mapping.py index 669681e3..9eb93379 100644 --- a/libervia/backend/memory/sqla_mapping.py +++ b/libervia/backend/memory/sqla_mapping.py @@ -615,12 +615,17 @@ class File(Base): class PubsubNode(Base): __tablename__ = "pubsub_nodes" - __table_args__ = (UniqueConstraint("profile_id", "service", "name"),) + __table_args__ = (UniqueConstraint("profile_id", "service", "name", "pep"),) id = Column(Integer, primary_key=True) profile_id = Column(ForeignKey("profiles.id", ondelete="CASCADE")) service = Column(JID) name = Column(Text, nullable=False) + pep = Column( + Boolean(create_constraint=True), + nullable=False, + server_default=false(), + ) access_model = Column( Enum( AccessModel,