diff --git a/src/infra/publication.py b/src/infra/publication.py index fb28357..1361abb 100644 --- a/src/infra/publication.py +++ b/src/infra/publication.py @@ -31,7 +31,7 @@ class PublicationSequenceModel(Base): position: Mapped[int] = mapped_column() entry_id: Mapped[int] = mapped_column(ForeignKey('publication_entry.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__ = ( UniqueConstraint('order_id', 'entry_id'), @@ -71,7 +71,7 @@ def model_to_sequence(db_pub_position: PublicationSequenceModel) -> Sequence: return Sequence( position=db_pub_position.position, entry=model_to_entry(db_pub_position.entry).to_profile(), - duration=db_pub_position.duration, + wait_duration=db_pub_position.wait_duration, ) diff --git a/src/infra/subscription.py b/src/infra/subscription.py index a5ae527..6c8131d 100644 --- a/src/infra/subscription.py +++ b/src/infra/subscription.py @@ -1,12 +1,12 @@ from dataclasses import asdict 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 datetime import datetime +from datetime import datetime, timedelta from src.infra.users import UserModel from sqlalchemy.orm import Mapped, mapped_column, relationship, Session, joinedload 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 ( SubscriptionRepo, Subscription, @@ -97,11 +97,67 @@ class SubscriptionRepoImpl(SubscriptionRepo, CRUDRepo[SubscriptionModel, Subscri return model_to_subscription(db_sub) 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 - # are available or not. - return {} + with Session(self._db) as session: + user = session.get(UserModel, user_id) + if user is None: + 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]: - # TODO: This needs to look at the sequences to decide which items are available and then filter that to ones - # that are newer than `sequence_notified` - return [] + with Session(self._db) as session: + user = session.get(UserModel, user_id) + if user is None: + 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 diff --git a/src/services/publications/data.py b/src/services/publications/data.py index c29a563..8580ea6 100644 --- a/src/services/publications/data.py +++ b/src/services/publications/data.py @@ -22,7 +22,7 @@ class Entry: class Sequence: position: int entry: EntryProfile - duration: timedelta | None + wait_duration: timedelta | None @dataclass