Skip to content

Commit 0ac0732

Browse files
authored
feat(opds): add per-user OPDS shelf exposure storage (DB schema + helpers)
Add app-db models for per-user OPDS exposure of regular and magic shelves, plus helper functions to read and update those selections. The change is backend-only and does not alter OPDS responses or UI behavior yet. It lays the groundwork for later filtering and user-facing controls. *** branch stack *** - Add per-user OPDS shelf exposure storage 👈 - Enforce per-user OPDS shelf filtering in backend - Add UI for per-user OPDS shelf exposure settings *** end branch stack ***
1 parent cc45596 commit 0ac0732

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

cps/ub.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ class User(UserBase, Base):
280280
remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
281281
view_settings = Column(JSON, default={})
282282
kobo_only_shelves_sync = Column(Integer, default=0)
283+
opds_only_shelves_sync = Column(Integer, default=0)
283284
hardcover_token = Column(String, unique=True, default=None)
284285
# New per-user theme (0=default/light, 1=caliBlur) replacing global-only behavior
285286
theme = Column(Integer, default=1)
@@ -322,6 +323,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
322323
def __init__(self):
323324
self.hardcover_token = None
324325
self.kobo_only_shelves_sync = None
326+
self.opds_only_shelves_sync = None
325327
self.view_settings = None
326328
self.allowed_column_value = None
327329
self.allowed_tags = None
@@ -354,6 +356,7 @@ def loadSettings(self):
354356
self.allowed_column_value = data.allowed_column_value
355357
self.view_settings = data.view_settings
356358
self.kobo_only_shelves_sync = data.kobo_only_shelves_sync
359+
self.opds_only_shelves_sync = data.opds_only_shelves_sync
357360
self.hardcover_token = data.hardcover_token
358361
self.auto_send_enabled = data.auto_send_enabled
359362
def role_admin(self):
@@ -462,6 +465,30 @@ class MagicShelfCache(Base):
462465
)
463466

464467

468+
class OpdsShelfExposure(Base):
469+
__tablename__ = 'opds_shelf_exposure'
470+
471+
id = Column(Integer, primary_key=True)
472+
user_id = Column(Integer, ForeignKey('user.id'), nullable=False, index=True)
473+
shelf_id = Column(Integer, ForeignKey('shelf.id'), nullable=False, index=True)
474+
475+
__table_args__ = (
476+
UniqueConstraint('user_id', 'shelf_id', name='unique_user_opds_shelf_exposure'),
477+
)
478+
479+
480+
class OpdsMagicShelfExposure(Base):
481+
__tablename__ = 'opds_magic_shelf_exposure'
482+
483+
id = Column(Integer, primary_key=True)
484+
user_id = Column(Integer, ForeignKey('user.id'), nullable=False, index=True)
485+
shelf_id = Column(Integer, ForeignKey('magic_shelf.id'), nullable=False, index=True)
486+
487+
__table_args__ = (
488+
UniqueConstraint('user_id', 'shelf_id', name='unique_user_opds_magic_shelf_exposure'),
489+
)
490+
491+
465492
class HiddenMagicShelfTemplate(Base):
466493
__tablename__ = 'hidden_magic_shelf_templates'
467494

@@ -575,6 +602,36 @@ class KoboSyncedBooks(Base):
575602
user_id = Column(Integer, ForeignKey('user.id'))
576603
book_id = Column(Integer)
577604

605+
606+
def is_opds_shelf_exposed_for_user(user_id, shelf_id, _session=None):
607+
s = _session if _session else session
608+
return s.query(OpdsShelfExposure).filter_by(user_id=user_id, shelf_id=shelf_id).first() is not None
609+
610+
611+
def set_opds_shelf_exposed_for_user(user_id, shelf_id, exposed, _session=None):
612+
s = _session if _session else session
613+
existing = s.query(OpdsShelfExposure).filter_by(user_id=user_id, shelf_id=shelf_id).first()
614+
if exposed:
615+
if existing is None:
616+
s.add(OpdsShelfExposure(user_id=user_id, shelf_id=shelf_id))
617+
elif existing is not None:
618+
s.delete(existing)
619+
620+
621+
def is_opds_magic_shelf_exposed_for_user(user_id, shelf_id, _session=None):
622+
s = _session if _session else session
623+
return s.query(OpdsMagicShelfExposure).filter_by(user_id=user_id, shelf_id=shelf_id).first() is not None
624+
625+
626+
def set_opds_magic_shelf_exposed_for_user(user_id, shelf_id, exposed, _session=None):
627+
s = _session if _session else session
628+
existing = s.query(OpdsMagicShelfExposure).filter_by(user_id=user_id, shelf_id=shelf_id).first()
629+
if exposed:
630+
if existing is None:
631+
s.add(OpdsMagicShelfExposure(user_id=user_id, shelf_id=shelf_id))
632+
elif existing is not None:
633+
s.delete(existing)
634+
578635
# The Kobo ReadingState API keeps track of 4 timestamped entities:
579636
# ReadingState, StatusInfo, Statistics, CurrentBookmark
580637
# Which we map to the following 4 tables:
@@ -794,6 +851,10 @@ def add_missing_tables(engine, _session):
794851
MagicShelf.__table__.create(bind=engine, checkfirst=True)
795852
if not engine.dialect.has_table(engine.connect(), "magic_shelf_cache"):
796853
MagicShelfCache.__table__.create(bind=engine, checkfirst=True)
854+
if not engine.dialect.has_table(engine.connect(), "opds_shelf_exposure"):
855+
OpdsShelfExposure.__table__.create(bind=engine, checkfirst=True)
856+
if not engine.dialect.has_table(engine.connect(), "opds_magic_shelf_exposure"):
857+
OpdsMagicShelfExposure.__table__.create(bind=engine, checkfirst=True)
797858
if not engine.dialect.has_table(engine.connect(), "hidden_magic_shelf_templates"):
798859
HiddenMagicShelfTemplate.__table__.create(bind=engine, checkfirst=True)
799860

@@ -834,6 +895,13 @@ def migrate_user_table(engine, _session):
834895
except exc.OperationalError: # Database is not compatible, some columns are missing
835896
_safe_session_rollback(_session, "user.hardcover_token")
836897
_run_ddl_with_retry(engine, "ALTER TABLE user ADD column 'hardcover_token' String")
898+
899+
try:
900+
_session.query(exists().where(User.opds_only_shelves_sync)).scalar()
901+
_session.commit()
902+
except exc.OperationalError:
903+
_safe_session_rollback(_session, "user.opds_only_shelves_sync")
904+
_run_ddl_with_retry(engine, "ALTER TABLE user ADD column 'opds_only_shelves_sync' Integer DEFAULT 0")
837905
# Migration for per-user theme column
838906
try:
839907
_session.query(exists().where(User.theme)).scalar()
@@ -1037,6 +1105,15 @@ def migrate_magic_shelf_table(engine, _session):
10371105
_run_ddl_with_retry(engine, "ALTER TABLE magic_shelf ADD column 'kobo_sync' Boolean DEFAULT 0")
10381106

10391107

1108+
def migrate_shelf_table(engine, _session):
1109+
try:
1110+
_session.query(exists().where(Shelf.kobo_sync)).scalar()
1111+
_session.commit()
1112+
except exc.OperationalError:
1113+
_safe_session_rollback(_session, "shelf.kobo_sync")
1114+
_run_ddl_with_retry(engine, "ALTER TABLE shelf ADD column 'kobo_sync' Boolean DEFAULT 0")
1115+
1116+
10401117
# Migrate database to current version, has to be updated after every database change. Currently migration from
10411118
# maybe 4/5 versions back to current should work.
10421119
# Migration is done by checking if relevant columns are existing, and then adding rows with SQL commands
@@ -1046,6 +1123,7 @@ def migrate_Database(_session):
10461123
migrate_registration_table(engine, _session)
10471124
migrate_user_session_table(engine, _session)
10481125
migrate_user_table(engine, _session)
1126+
migrate_shelf_table(engine, _session)
10491127
migrate_oauth_provider_table(engine, _session)
10501128
migrate_config_table(engine, _session)
10511129
migrate_magic_shelf_table(engine, _session)

0 commit comments

Comments
 (0)