Compare commits

..

2 commits

Author SHA1 Message Date
Campbell Alden
04440e6270 Fill in the remaining implementations for the subscription infra impl 2026-08-29 00:52:40 +09:00
Campbell Alden
94c03cad1f Rename duration to wait_duration to make it clear what the duration means
The wait_duration means how long after the previous entry was released
that you have to wait for the next one.
2026-08-29 00:52:28 +09:00
3 changed files with 68 additions and 12 deletions

View file

@ -31,7 +31,7 @@ class PublicationSequenceModel(Base):
position: Mapped[int] = mapped_column() position: Mapped[int] = mapped_column()
entry_id: Mapped[int] = mapped_column(ForeignKey('publication_entry.id')) entry_id: Mapped[int] = mapped_column(ForeignKey('publication_entry.id'))
order_id: Mapped[int] = mapped_column(ForeignKey('publication_order.id')) order_id: Mapped[int] = mapped_column(ForeignKey('publication_order.id'))
duration: Mapped[timedelta | None] = mapped_column(Interval()) wait_duration: Mapped[timedelta | None] = mapped_column(Interval())
__table_args__ = ( __table_args__ = (
UniqueConstraint('order_id', 'entry_id'), UniqueConstraint('order_id', 'entry_id'),
@ -71,7 +71,7 @@ def model_to_sequence(db_pub_position: PublicationSequenceModel) -> Sequence:
return Sequence( return Sequence(
position=db_pub_position.position, position=db_pub_position.position,
entry=model_to_entry(db_pub_position.entry).to_profile(), entry=model_to_entry(db_pub_position.entry).to_profile(),
duration=db_pub_position.duration, wait_duration=db_pub_position.wait_duration,
) )

View file

@ -1,12 +1,12 @@
from dataclasses import asdict from dataclasses import asdict
import logging import logging
from src.infra.publication import model_to_order from src.infra.publication import model_to_order, model_to_publication, model_to_sequence, model_to_order_profile
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from datetime import datetime from datetime import datetime, timedelta
from src.infra.users import UserModel from src.infra.users import UserModel
from sqlalchemy.orm import Mapped, mapped_column, relationship, Session, joinedload from sqlalchemy.orm import Mapped, mapped_column, relationship, Session, joinedload
from src.infra.db import Base, CRUDRepo from src.infra.db import Base, CRUDRepo
from sqlalchemy import Engine, ForeignKey, CheckConstraint, DateTime, func, UniqueConstraint, select, delete from sqlalchemy import Engine, ForeignKey, CheckConstraint, DateTime, func, UniqueConstraint, select
from src.services.subscription import ( from src.services.subscription import (
SubscriptionRepo, SubscriptionRepo,
Subscription, Subscription,
@ -97,11 +97,67 @@ class SubscriptionRepoImpl(SubscriptionRepo, CRUDRepo[SubscriptionModel, Subscri
return model_to_subscription(db_sub) return model_to_subscription(db_sub)
def get_available_entries(self, user_id: int) -> dict[SubscriptionId, list[AvailableSubscriptionEntry]]: def get_available_entries(self, user_id: int) -> dict[SubscriptionId, list[AvailableSubscriptionEntry]]:
# TODO: This needs to actually look at the sequence seen and the entry release times to work out which items with Session(self._db) as session:
# are available or not. user = session.get(UserModel, user_id)
if user is None:
return {} return {}
available = {}
for sub in user.subscriptions:
publication = model_to_publication(sub.order.publication)
available_entries = []
# This will walk through the order and check if enough time has elapsed since the start time to
# include each entry
elapsed = timedelta(days=0)
now = datetime.now(tz=sub.start.tzinfo)
for entry in sub.order.sequence_entries:
elapsed += entry.wait_duration or timedelta(days=0)
if sub.start + elapsed <= now:
available_entries.append(
AvailableSubscriptionEntry(
publication=publication,
available_since=sub.start + elapsed,
order=model_to_order_profile(sub.order),
sequence=model_to_sequence(entry),
)
)
else:
# If the amount of time that has elapsed for the current entry we're checking would be in the
# future, then at that point there's no need to check any other entries.
break
available[sub.id] = available_entries
return available
def get_unnotified_available_subscription_entries(self, user_id: int) -> list[AvailableSubscriptionEntry]: def get_unnotified_available_subscription_entries(self, user_id: int) -> list[AvailableSubscriptionEntry]:
# TODO: This needs to look at the sequences to decide which items are available and then filter that to ones with Session(self._db) as session:
# that are newer than `sequence_notified` user = session.get(UserModel, user_id)
if user is None:
return [] return []
available_unseen = []
for sub in user.subscriptions:
publication = model_to_publication(sub.order.publication)
# This will walk through the order and check if enough time has elapsed since the start time to
# include each entry
elapsed = timedelta(days=0)
now = datetime.now(tz=sub.start.tzinfo)
for entry in sub.order.sequence_entries:
elapsed += entry.wait_duration or timedelta(days=0)
if sub.start + elapsed <= now and sub.sequence_notified < entry.position:
available_unseen.append(
AvailableSubscriptionEntry(
publication=publication,
available_since=sub.start + elapsed,
order=model_to_order_profile(sub.order),
sequence=model_to_sequence(entry),
)
)
else:
# If the amount of time that has elapsed for the current entry we're checking would be in the
# future, then at that point there's no need to check any other entries.
break
return available_unseen

View file

@ -22,7 +22,7 @@ class Entry:
class Sequence: class Sequence:
position: int position: int
entry: EntryProfile entry: EntryProfile
duration: timedelta | None wait_duration: timedelta | None
@dataclass @dataclass