Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model for following an account #1599

Open
jace opened this issue Jan 24, 2023 · 7 comments
Open

Model for following an account #1599

jace opened this issue Jan 24, 2023 · 7 comments
Assignees

Comments

@jace
Copy link
Member

jace commented Jan 24, 2023

A User may follow an Account (nee Profile) to be appraised of new activity and receive updates.

At its core, this is an instance of a membership model, subclassing ImmutableUserMembershipMixin. However, there are four additional requirements:

  1. The User may have preferred contacts (email or phone) for delivery of updates from this Account. These should be included in the record as foreign keys to either UserEmail/UserPhone (ensuring ownership by this user) or EmailAddress/PhoneNumber (see below).

  2. The User may choose to share zero, one or both of these contacts with the Account, with the explicit understanding the Account may export these contacts into an external contact management system that the User no longer has any control over.

  3. The User may have authorised the Account off-platform to add them as a follower, but the Account only has contact details, not a user identifier. In this case, the membership record may have no foreign key reference to the User (and hence cannot depend on ImmutableUserMembershipMixin). The email/phone identifiers belong to the Account and not the User.

  4. The User may be required to hold a paid subscription to the Account. Is the source of truth for having a subscription in this record itself, or is it external and referenced from here?

In summary, we have three competing paradigms that are very similar in their data structures but are functionally distinct concepts:

  1. A follower record that is owned by the User and contains their preferences. The User may optionally choose to share their contacts with the Account.
  2. A list record that is owned by the Account and does not involve consent from the User.
  3. A subscription record that indicates a financial commitment between the User and the Account.

Should these be implemented differently, or unified with some compromises? Discussion in comments.

@jace jace self-assigned this Jan 24, 2023
@jace
Copy link
Member Author

jace commented Jan 24, 2023

Subscriptions have several complications and are best managed as independent records:

  1. The purchase and refund processes need distinct records, as do the tax receipts. In Boxoffice, these are an Order with one or more Transaction records, with tax receipts off-site.
  2. A subscription may be for a variable period (monthly, annual, multiple years). Membership records are "fixed state" — active until explicitly revoked – so either we change membership records to conditional state, or we keep subscriptions separate and batch-expire membership records.
  3. Individual subscriptions are immediately active and cannot co-exist with another subscription, so a second purchase attempt must be blocked. However, bulk subscriptions are a required feature, and a purchase manager should be able to assign and re-assign. Since the purchase manager cannot consent on behalf of a User, this introduces a third actor in the membership record if they are the same record.
  4. The clock on a bulk subscription may need to be linked to its assignment rather than its purchase. If a subscription is purchased on January 1 but not assigned until February 1:
    1. The subscription expires on January 1 the following year, placing pressure on the purchase manager to assign it or lose it.
    2. The subscription expires on February 1 the following year, reflecting the assigned time. In this case, there should be an upper cap on unassigned time.

@jace
Copy link
Member Author

jace commented Jan 24, 2023

The list use case is incompatible with our notifications framework, as that requires an explicit User. We can reduce scope to just the follower use case, with optional columns for preferred contacts and optional sharing of contacts with the Account.

An Account can still invite someone to be a follower.

@jace
Copy link
Member Author

jace commented Jan 24, 2023

Counterpoint: Lists are still required for change announcements, but can be simplified to a list of accounts, without contact information, but with custom extra fields.

This still leaves open questions:

  1. Is a list the same data structure as a follower, or different?

  2. A member of a list can choose to stop communications. However, is there a way for someone to find available lists, join one, or leave the list instead of muting it?

    These options should not be available for change announcements, where lists are constructed explicitly for affected users and the communication is mandatory, but there is overlap with marketing lists.

  3. What is the overlap between lists and subscriber-exclusive content (a "tier"). Is that also a type of list or a type of follower or something else?

@jace
Copy link
Member Author

jace commented Feb 13, 2023

Simplified version of a FollowerMembership model:

class FollowerMembership(ImmutableUserMembershipMixin, EmailAddressMixin, PhoneNumberMixin, db.Model):
    __email_for__ = 'user'
    __email_optional__ = True
    __email_is_exclusive__ = True
    __phone_for__ = 'user'
    __phone_optional__ = True
    __phone_is_exclusive__ = True
    account = relationship(Profile)  # Rename model to `Account` soon

    @property
    def phone_number_reference_is_active(self) -> bool:
        return self.is_active

    @property
    def email_address_reference_is_active(self) -> bool:
        return self.is_active

In this model, there is no correlation with subscription, and that has been left for the future. Sharing an email or phone is optional, but if shared it must be validated to belong to the user, and will also redirect notifications to the preferred address, thereby solving for the currently-pending context parameter in the User.transport_for_* methods.

When a UserPhone or UserEmail is removed, any active FollowerMembership instances using the same underlying EmailAddress or PhoneNumber must be replaced with (a) no share, or (b) user's new choice as confirmed in the delete form.

EmailAddressMixin and PhoneNumberMixin don't handle conditional indexes as used by membership models. The __*_is_exclusive__ references are only used for refcounting. Conditional indexing can be handled directly in the FollowerMembership model, but the mixins have to be upgraded to not add their own indexes (currently: choice of index or unique constraint, no choice to skip both).

@jace
Copy link
Member Author

jace commented Feb 23, 2023

Implementation is in #1644.

@jace
Copy link
Member Author

jace commented Nov 22, 2023

After the merger of User, Organization and Profile into Account in #1519 and #1697, there is a single AccountMembership model that currently represents the owner and admin roles, but can be extended to support follower and subscriber roles.

@jace
Copy link
Member Author

jace commented Apr 16, 2024

#1644 has been superseded by a new implementation in #2005.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant