slidge 0.1.2__py3-none-any.whl → 0.2.0a0__py3-none-any.whl

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.
Files changed (63) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -196
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +8 -1
  5. slidge/command/admin.py +5 -6
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -5
  9. slidge/contact/contact.py +93 -31
  10. slidge/contact/roster.py +54 -39
  11. slidge/core/config.py +13 -7
  12. slidge/core/gateway/base.py +139 -34
  13. slidge/core/gateway/disco.py +2 -4
  14. slidge/core/gateway/mam.py +1 -4
  15. slidge/core/gateway/ping.py +2 -3
  16. slidge/core/gateway/presence.py +1 -1
  17. slidge/core/gateway/registration.py +32 -21
  18. slidge/core/gateway/search.py +3 -5
  19. slidge/core/gateway/session_dispatcher.py +109 -51
  20. slidge/core/gateway/vcard_temp.py +6 -4
  21. slidge/core/mixins/__init__.py +11 -1
  22. slidge/core/mixins/attachment.py +15 -10
  23. slidge/core/mixins/avatar.py +66 -18
  24. slidge/core/mixins/base.py +8 -2
  25. slidge/core/mixins/message.py +11 -7
  26. slidge/core/mixins/message_maker.py +17 -9
  27. slidge/core/mixins/presence.py +14 -4
  28. slidge/core/pubsub.py +54 -212
  29. slidge/core/session.py +65 -33
  30. slidge/db/__init__.py +4 -0
  31. slidge/db/alembic/env.py +64 -0
  32. slidge/db/alembic/script.py.mako +26 -0
  33. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  34. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  35. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  36. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +76 -0
  37. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  38. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  39. slidge/db/avatar.py +224 -0
  40. slidge/db/meta.py +65 -0
  41. slidge/db/models.py +365 -0
  42. slidge/db/store.py +976 -0
  43. slidge/group/archive.py +13 -14
  44. slidge/group/bookmarks.py +59 -56
  45. slidge/group/participant.py +81 -29
  46. slidge/group/room.py +242 -142
  47. slidge/main.py +201 -0
  48. slidge/migration.py +30 -0
  49. slidge/slixfix/__init__.py +35 -2
  50. slidge/slixfix/roster.py +11 -4
  51. slidge/slixfix/xep_0292/vcard4.py +1 -0
  52. slidge/util/db.py +1 -47
  53. slidge/util/test.py +21 -4
  54. slidge/util/types.py +24 -4
  55. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/METADATA +3 -1
  56. slidge-0.2.0a0.dist-info/RECORD +108 -0
  57. slidge/core/cache.py +0 -183
  58. slidge/util/schema.sql +0 -126
  59. slidge/util/sql.py +0 -508
  60. slidge-0.1.2.dist-info/RECORD +0 -96
  61. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/entry_points.txt +0 -0
slidge/core/session.py CHANGED
@@ -18,11 +18,10 @@ from slixmpp.types import PresenceShows
18
18
 
19
19
  from ..command import SearchResult
20
20
  from ..contact import LegacyContact, LegacyRoster
21
+ from ..db.models import GatewayUser
21
22
  from ..group.bookmarks import LegacyBookmarks
22
23
  from ..group.room import LegacyMUC
23
24
  from ..util import ABCSubclassableOnceAtMost
24
- from ..util.db import GatewayUser, user_store
25
- from ..util.sql import SQLBiDict
26
25
  from ..util.types import (
27
26
  LegacyGroupIdType,
28
27
  LegacyMessageType,
@@ -92,16 +91,10 @@ class BaseSession(
92
91
  """
93
92
 
94
93
  def __init__(self, user: GatewayUser):
95
- self.log = logging.getLogger(user.bare_jid)
94
+ self.log = logging.getLogger(user.jid.bare)
96
95
 
97
- self.user = user
98
- self.sent = SQLBiDict[LegacyMessageType, str](
99
- "session_message_sent", "legacy_id", "xmpp_id", self.user
100
- )
101
- # message ids (*not* stanza-ids), needed for last msg correction
102
- self.muc_sent_msg_ids = SQLBiDict[LegacyMessageType, str](
103
- "session_message_sent_muc", "legacy_id", "xmpp_id", self.user
104
- )
96
+ self.user_jid = user.jid
97
+ self.user_pk = user.id
105
98
 
106
99
  self.ignore_messages = set[str]()
107
100
 
@@ -115,26 +108,26 @@ class BaseSession(
115
108
 
116
109
  self.http = self.xmpp.http
117
110
 
118
- self.threads = SQLBiDict[str, LegacyThreadType]( # type:ignore
119
- "session_thread_sent_muc", "legacy_id", "xmpp_id", self.user
120
- )
121
111
  self.thread_creation_lock = asyncio.Lock()
122
112
 
123
113
  self.__cached_presence: Optional[CachedPresence] = None
124
114
 
125
- self.avatar_hash: Optional[str] = None
126
-
127
115
  self.__tasks = set[asyncio.Task]()
128
116
 
117
+ @property
118
+ def user(self) -> GatewayUser:
119
+ return self.xmpp.store.users.get(self.user_jid) # type:ignore
120
+
129
121
  def __remove_task(self, fut):
130
122
  self.log.debug("Removing fut %s", fut)
131
123
  self.__tasks.remove(fut)
132
124
 
133
- def create_task(self, coro) -> None:
125
+ def create_task(self, coro) -> asyncio.Task:
134
126
  task = self.xmpp.loop.create_task(coro)
135
127
  self.__tasks.add(task)
136
128
  self.log.debug("Creating task %s", task)
137
129
  task.add_done_callback(lambda _: self.__remove_task(task))
130
+ return task
138
131
 
139
132
  def cancel_all_tasks(self):
140
133
  for task in self.__tasks:
@@ -488,6 +481,17 @@ class BaseSession(
488
481
  """
489
482
  await muc.on_set_affiliation(contact, "member", reason, None)
490
483
 
484
+ async def on_leave_group(self, muc: LegacyMUC):
485
+ """
486
+ Triggered when the user leaves a group via the dedicated slidge command
487
+ or the :xep:`0077` ``<remove />`` mechanism.
488
+
489
+ This should be interpreted as definitely leaving the group.
490
+
491
+ :param muc: The group to leave
492
+ """
493
+ raise NotImplementedError
494
+
491
495
  def __reset_ready(self):
492
496
  self.ready = self.xmpp.loop.create_future()
493
497
 
@@ -507,7 +511,7 @@ class BaseSession(
507
511
  self.ready.set_result(True)
508
512
 
509
513
  def __repr__(self):
510
- return f"<Session of {self.user}>"
514
+ return f"<Session of {self.user_jid}>"
511
515
 
512
516
  def shutdown(self) -> asyncio.Task:
513
517
  for c in self.contacts:
@@ -571,9 +575,9 @@ class BaseSession(
571
575
  log.debug("user not found", stack_info=True)
572
576
  raise XMPPError(text="User not found", condition="subscription-required")
573
577
 
574
- session = _sessions.get(user)
578
+ session = _sessions.get(user.jid.bare)
575
579
  if session is None:
576
- _sessions[user] = session = cls(user)
580
+ _sessions[user.jid.bare] = session = cls(user)
577
581
  return session
578
582
 
579
583
  @classmethod
@@ -590,7 +594,7 @@ class BaseSession(
590
594
  # :param s:
591
595
  # :return:
592
596
  # """
593
- return cls._from_user_or_none(user_store.get_by_stanza(s))
597
+ return cls.from_jid(s.get_from())
594
598
 
595
599
  @classmethod
596
600
  def from_jid(cls, jid: JID) -> "BaseSession":
@@ -602,7 +606,11 @@ class BaseSession(
602
606
  # :param jid:
603
607
  # :return:
604
608
  # """
605
- return cls._from_user_or_none(user_store.get_by_jid(jid))
609
+ session = _sessions.get(jid.bare)
610
+ if session is not None:
611
+ return session
612
+ user = cls.xmpp.store.users.get(jid)
613
+ return cls._from_user_or_none(user)
606
614
 
607
615
  @classmethod
608
616
  async def kill_by_jid(cls, jid: JID):
@@ -615,16 +623,21 @@ class BaseSession(
615
623
  # :return:
616
624
  # """
617
625
  log.debug("Killing session of %s", jid)
618
- for user, session in _sessions.items():
619
- if user.jid == jid.bare:
626
+ for user_jid, session in _sessions.items():
627
+ if user_jid == jid.bare:
620
628
  break
621
629
  else:
622
630
  log.debug("Did not find a session for %s", jid)
623
631
  return
624
632
  for c in session.contacts:
625
633
  c.unsubscribe()
634
+ user = cls.xmpp.store.users.get(jid)
635
+ if user is None:
636
+ log.warning("User not found during unregistration")
637
+ return
626
638
  await cls.xmpp.unregister(user)
627
- del _sessions[user]
639
+ cls.xmpp.store.users.delete(user.jid)
640
+ del _sessions[user.jid.bare]
628
641
  del user
629
642
  del session
630
643
 
@@ -649,7 +662,7 @@ class BaseSession(
649
662
  """
650
663
  self.__cached_presence = CachedPresence(status, show, kwargs)
651
664
  self.xmpp.send_presence(
652
- pto=self.user.bare_jid, pstatus=status, pshow=show, **kwargs
665
+ pto=self.user_jid.bare, pstatus=status, pshow=show, **kwargs
653
666
  )
654
667
 
655
668
  def send_cached_presence(self, to: JID):
@@ -671,7 +684,7 @@ class BaseSession(
671
684
 
672
685
  :param text: A text
673
686
  """
674
- self.xmpp.send_text(text, mto=self.user.jid, **msg_kwargs)
687
+ self.xmpp.send_text(text, mto=self.user_jid, **msg_kwargs)
675
688
 
676
689
  def send_gateway_invite(
677
690
  self,
@@ -686,7 +699,7 @@ class BaseSession(
686
699
  :param reason:
687
700
  :param password:
688
701
  """
689
- self.xmpp.invite_to(muc, reason=reason, password=password, mto=self.user.jid)
702
+ self.xmpp.invite_to(muc, reason=reason, password=password, mto=self.user_jid)
690
703
 
691
704
  async def input(self, text: str, **msg_kwargs):
692
705
  """
@@ -698,7 +711,7 @@ class BaseSession(
698
711
  :param msg_kwargs: Extra attributes
699
712
  :return:
700
713
  """
701
- return await self.xmpp.input(self.user.jid, text, **msg_kwargs)
714
+ return await self.xmpp.input(self.user_jid, text, **msg_kwargs)
702
715
 
703
716
  async def send_qr(self, text: str):
704
717
  """
@@ -707,7 +720,7 @@ class BaseSession(
707
720
 
708
721
  :param text: Text to encode as a QR code
709
722
  """
710
- await self.xmpp.send_qr(text, mto=self.user.jid)
723
+ await self.xmpp.send_qr(text, mto=self.user_jid)
711
724
 
712
725
  def re_login(self):
713
726
  # Logout then re-login
@@ -718,8 +731,8 @@ class BaseSession(
718
731
  async def get_contact_or_group_or_participant(self, jid: JID):
719
732
  if jid.bare in (contacts := self.contacts.known_contacts(only_friends=False)):
720
733
  return contacts[jid.bare]
721
- if jid.bare in (mucs := self.bookmarks._mucs_by_bare_jid):
722
- return await self.__get_muc_or_participant(mucs[jid.bare], jid)
734
+ if (muc := self.bookmarks.by_jid_only_if_exists(JID(jid.bare))) is not None:
735
+ return await self.__get_muc_or_participant(muc, jid)
723
736
  else:
724
737
  muc = None
725
738
 
@@ -763,6 +776,25 @@ class BaseSession(
763
776
  "Legacy session is not fully initialized, retry later",
764
777
  )
765
778
 
779
+ def legacy_module_data_update(self, data: dict):
780
+ with self.xmpp.store.session():
781
+ user = self.user
782
+ user.legacy_module_data.update(data)
783
+ self.xmpp.store.users.update(user)
784
+
785
+ def legacy_module_data_set(self, data: dict):
786
+ with self.xmpp.store.session():
787
+ user = self.user
788
+ user.legacy_module_data = data
789
+ self.xmpp.store.users.update(user)
790
+
791
+ def legacy_module_data_clear(self):
792
+ with self.xmpp.store.session():
793
+ user = self.user
794
+ user.legacy_module_data.clear()
795
+ self.xmpp.store.users.update(user)
796
+
766
797
 
767
- _sessions: dict[GatewayUser, BaseSession] = {}
798
+ # keys = user.jid.bare
799
+ _sessions: dict[str, BaseSession] = {}
768
800
  log = logging.getLogger(__name__)
slidge/db/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .models import GatewayUser
2
+ from .store import SlidgeStore
3
+
4
+ __all__ = ("GatewayUser", "SlidgeStore")
@@ -0,0 +1,64 @@
1
+ from alembic import context
2
+
3
+ from slidge import global_config
4
+ from slidge.db.meta import Base, get_engine
5
+
6
+ config = context.config
7
+
8
+ target_metadata = Base.metadata
9
+
10
+
11
+ def run_migrations_offline() -> None:
12
+ """Run migrations in 'offline' mode.
13
+
14
+ This configures the context with just a URL
15
+ and not an Engine, though an Engine is acceptable
16
+ here as well. By skipping the Engine creation
17
+ we don't even need a DBAPI to be available.
18
+
19
+ Calls to context.execute() here emit the given string to the
20
+ script output.
21
+
22
+ """
23
+ url = config.get_main_option("sqlalchemy.url")
24
+ context.configure(
25
+ url=url,
26
+ target_metadata=target_metadata,
27
+ literal_binds=True,
28
+ dialect_opts={"paramstyle": "named"},
29
+ render_as_batch=True,
30
+ )
31
+
32
+ with context.begin_transaction():
33
+ context.run_migrations()
34
+
35
+
36
+ def run_migrations_online() -> None:
37
+ """Run migrations in 'online' mode.
38
+
39
+ In this scenario we need to create an Engine
40
+ and associate a connection with the context.
41
+
42
+ """
43
+ try:
44
+ # in prod
45
+ connectable = get_engine(global_config.DB_URL)
46
+ except AttributeError:
47
+ # during dev, to generate migrations
48
+ connectable = get_engine("sqlite+pysqlite:///dev/slidge.sqlite")
49
+
50
+ with connectable.connect() as connection:
51
+ context.configure(
52
+ connection=connection,
53
+ target_metadata=target_metadata,
54
+ render_as_batch=True,
55
+ )
56
+
57
+ with context.begin_transaction():
58
+ context.run_migrations()
59
+
60
+
61
+ if context.is_offline_mode():
62
+ run_migrations_offline()
63
+ else:
64
+ run_migrations_online()
@@ -0,0 +1,26 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ ${upgrades if upgrades else "pass"}
23
+
24
+
25
+ def downgrade() -> None:
26
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,36 @@
1
+ """Add n_participants attributes to Room
2
+
3
+ Should have been part of another commit, but I messed up some rebase
4
+
5
+ Revision ID: 09f27f098baa
6
+ Revises: 29f5280c61aa
7
+ Create Date: 2024-07-11 10:54:21.155871
8
+
9
+ """
10
+
11
+ from typing import Sequence, Union
12
+
13
+ import sqlalchemy as sa
14
+ from alembic import op
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "09f27f098baa"
18
+ down_revision: Union[str, None] = "29f5280c61aa"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ with op.batch_alter_table("room", schema=None) as batch_op:
26
+ batch_op.add_column(sa.Column("n_participants", sa.Integer(), nullable=True))
27
+
28
+ # ### end Alembic commands ###
29
+
30
+
31
+ def downgrade() -> None:
32
+ # ### commands auto generated by Alembic - please adjust! ###
33
+ with op.batch_alter_table("room", schema=None) as batch_op:
34
+ batch_op.drop_column("n_participants")
35
+
36
+ # ### end Alembic commands ###
@@ -0,0 +1,37 @@
1
+ """Store subject setter in Room
2
+
3
+ Revision ID: 29f5280c61aa
4
+ Revises: 8d2ced764698
5
+ Create Date: 2024-07-10 13:09:25.181594
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "29f5280c61aa"
16
+ down_revision: Union[str, None] = "8d2ced764698"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ with op.batch_alter_table("room", schema=None) as batch_op:
23
+ batch_op.add_column(sa.Column("subject_setter_id", sa.Integer(), nullable=True))
24
+ # we give this constraint a name a workaround for
25
+ # https://github.com/sqlalchemy/alembic/issues/1195
26
+ batch_op.create_foreign_key(
27
+ "subject_setter_id_foreign_key",
28
+ "participant",
29
+ ["subject_setter_id"],
30
+ ["id"],
31
+ )
32
+
33
+
34
+ def downgrade() -> None:
35
+ with op.batch_alter_table("room", schema=None) as batch_op:
36
+ batch_op.drop_constraint("subject_setter_id_foreign_key", type_="foreignkey")
37
+ batch_op.drop_column("subject_setter_id")
@@ -0,0 +1,133 @@
1
+ """Rely on DB to store contacts, rooms and participants
2
+
3
+ Revision ID: 8d2ced764698
4
+ Revises: b33993e87db3
5
+ Create Date: 2024-07-08 14:39:47.022088
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ import slidge.db.meta
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "8d2ced764698"
18
+ down_revision: Union[str, None] = "b33993e87db3"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ op.create_table(
25
+ "hat",
26
+ sa.Column("id", sa.Integer(), nullable=False),
27
+ sa.Column("title", sa.String(), nullable=False),
28
+ sa.Column("uri", sa.String(), nullable=False),
29
+ sa.PrimaryKeyConstraint("id"),
30
+ sa.UniqueConstraint("title", "uri"),
31
+ )
32
+ op.create_table(
33
+ "contact_sent",
34
+ sa.Column("id", sa.Integer(), nullable=False),
35
+ sa.Column("contact_id", sa.Integer(), nullable=False),
36
+ sa.Column("msg_id", sa.String(), nullable=False),
37
+ sa.ForeignKeyConstraint(
38
+ ["contact_id"],
39
+ ["contact.id"],
40
+ ),
41
+ sa.PrimaryKeyConstraint("id"),
42
+ sa.UniqueConstraint("contact_id", "msg_id"),
43
+ )
44
+ op.create_table(
45
+ "participant",
46
+ sa.Column("id", sa.Integer(), nullable=False),
47
+ sa.Column("room_id", sa.Integer(), nullable=False),
48
+ sa.Column("contact_id", sa.Integer(), nullable=True),
49
+ sa.Column("is_user", sa.Boolean(), nullable=False),
50
+ sa.Column(
51
+ "affiliation",
52
+ sa.Enum("outcast", "member", "admin", "owner", "none", native_enum=False),
53
+ nullable=False,
54
+ ),
55
+ sa.Column(
56
+ "role",
57
+ sa.Enum("moderator", "participant", "visitor", "none", native_enum=False),
58
+ nullable=False,
59
+ ),
60
+ sa.Column("presence_sent", sa.Boolean(), nullable=False),
61
+ sa.Column("resource", sa.String(), nullable=True),
62
+ sa.Column("nickname", sa.String(), nullable=True),
63
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
64
+ sa.ForeignKeyConstraint(
65
+ ["contact_id"],
66
+ ["contact.id"],
67
+ ),
68
+ sa.ForeignKeyConstraint(
69
+ ["room_id"],
70
+ ["room.id"],
71
+ ),
72
+ sa.PrimaryKeyConstraint("id"),
73
+ )
74
+ op.create_table(
75
+ "participant_hats",
76
+ sa.Column("participant_id", sa.Integer(), nullable=False),
77
+ sa.Column("hat_id", sa.Integer(), nullable=False),
78
+ sa.ForeignKeyConstraint(
79
+ ["hat_id"],
80
+ ["hat.id"],
81
+ ),
82
+ sa.ForeignKeyConstraint(
83
+ ["participant_id"],
84
+ ["participant.id"],
85
+ ),
86
+ sa.PrimaryKeyConstraint("participant_id", "hat_id"),
87
+ )
88
+ op.add_column("contact", sa.Column("is_friend", sa.Boolean(), nullable=False))
89
+ op.add_column("contact", sa.Column("added_to_roster", sa.Boolean(), nullable=False))
90
+ op.add_column(
91
+ "contact",
92
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
93
+ )
94
+ op.add_column("contact", sa.Column("updated", sa.Boolean(), nullable=False))
95
+ op.add_column("room", sa.Column("description", sa.String(), nullable=True))
96
+ op.add_column("room", sa.Column("subject", sa.String(), nullable=True))
97
+ op.add_column("room", sa.Column("subject_date", sa.DateTime(), nullable=True))
98
+ op.add_column(
99
+ "room",
100
+ sa.Column(
101
+ "muc_type",
102
+ sa.Enum("GROUP", "CHANNEL", "CHANNEL_NON_ANONYMOUS", name="muctype"),
103
+ nullable=True,
104
+ ),
105
+ )
106
+ op.add_column("room", sa.Column("user_resources", sa.String(), nullable=True))
107
+ op.add_column(
108
+ "room", sa.Column("participants_filled", sa.Boolean(), nullable=False)
109
+ )
110
+ op.add_column(
111
+ "room",
112
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
113
+ )
114
+ op.add_column("room", sa.Column("updated", sa.Boolean(), nullable=False))
115
+
116
+
117
+ def downgrade() -> None:
118
+ op.drop_column("room", "updated")
119
+ op.drop_column("room", "extra_attributes")
120
+ op.drop_column("room", "participants_filled")
121
+ op.drop_column("room", "user_resources")
122
+ op.drop_column("room", "muc_type")
123
+ op.drop_column("room", "subject_date")
124
+ op.drop_column("room", "subject")
125
+ op.drop_column("room", "description")
126
+ op.drop_column("contact", "updated")
127
+ op.drop_column("contact", "extra_attributes")
128
+ op.drop_column("contact", "added_to_roster")
129
+ op.drop_column("contact", "is_friend")
130
+ op.drop_table("participant_hats")
131
+ op.drop_table("participant")
132
+ op.drop_table("contact_sent")
133
+ op.drop_table("hat")
@@ -0,0 +1,76 @@
1
+ """DB Creation
2
+
3
+ Including a migration from the user_store shelf
4
+
5
+ Revision ID: aa9d82a7f6ef
6
+ Revises:
7
+ Create Date: 2024-04-17 20:57:01.357041
8
+
9
+ """
10
+
11
+ import logging
12
+ from typing import Sequence, Union
13
+
14
+ import sqlalchemy as sa
15
+ from alembic import op
16
+
17
+ import slidge.db.meta
18
+
19
+ # revision identifiers, used by Alembic.
20
+ revision: str = "aa9d82a7f6ef"
21
+ down_revision: Union[str, None] = None
22
+ branch_labels: Union[str, Sequence[str], None] = None
23
+ depends_on: Union[str, Sequence[str], None] = None
24
+
25
+
26
+ def upgrade() -> None:
27
+ # ### commands auto generated by Alembic - please adjust! ###
28
+ accounts = op.create_table(
29
+ "user_account",
30
+ sa.Column("id", sa.Integer(), nullable=False),
31
+ sa.Column("jid", slidge.db.meta.JIDType(), nullable=False),
32
+ sa.Column(
33
+ "registration_date",
34
+ sa.DateTime(),
35
+ server_default=sa.text("(CURRENT_TIMESTAMP)"),
36
+ nullable=False,
37
+ ),
38
+ sa.Column(
39
+ "legacy_module_data", slidge.db.meta.JSONEncodedDict(), nullable=False
40
+ ),
41
+ sa.Column("preferences", slidge.db.meta.JSONEncodedDict(), nullable=False),
42
+ sa.PrimaryKeyConstraint("id"),
43
+ sa.UniqueConstraint("jid"),
44
+ )
45
+ # ### end Alembic commands ###
46
+ migrate_from_shelf(accounts)
47
+
48
+
49
+ def downgrade() -> None:
50
+ # ### commands auto generated by Alembic - please adjust! ###
51
+ op.drop_table("user_account")
52
+ # ### end Alembic commands ###
53
+
54
+
55
+ def migrate_from_shelf(accounts: sa.Table) -> None:
56
+ try:
57
+ from slidge.util.db import user_store
58
+ except ImportError:
59
+ return
60
+ try:
61
+ users = list(user_store.get_all())
62
+ except AttributeError:
63
+ return
64
+ logging.info("Migrating %s users from the deprecated user_store shelf", len(users))
65
+ op.bulk_insert(
66
+ accounts,
67
+ [
68
+ {
69
+ "jid": user.jid,
70
+ "registration_date": user.registration_date,
71
+ "legacy_module_data": user.registration_form,
72
+ "preferences": {},
73
+ }
74
+ for user in users
75
+ ],
76
+ )