=========
Addresses
=========

The REST API can be used to manage addresses.

There are no addresses yet.

    >>> from mailman.testing.documentation import dump_json
    >>> dump_json('http://localhost:9001/3.0/addresses')
    http_etag: "..."
    start: 0
    total_size: 0

When an address is created via the internal API, it is available in the REST
API.
::

    >>> from zope.component import getUtility
    >>> from mailman.interfaces.usermanager import IUserManager
    >>> user_manager = getUtility(IUserManager)
    >>> anne = user_manager.create_address('anne@example.com')
    >>> from mailman.config import config
    >>> transaction = config.db
    >>> transaction.commit()

    >>> dump_json('http://localhost:9001/3.0/addresses')
    entry 0:
        email: anne@example.com
        http_etag: "..."
        original_email: anne@example.com
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.0/addresses/anne@example.com
    http_etag: "..."
    start: 0
    total_size: 1

Anne's address can also be accessed directly.

    >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com')
    email: anne@example.com
    http_etag: "..."
    original_email: anne@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/anne@example.com

Bart registers with a mixed-case address.  The canonical URL always includes
the lower-case version.

    >>> bart = user_manager.create_address('Bart.Person@example.com')
    >>> transaction.commit()
    >>> dump_json(
    ...     'http://localhost:9001/3.0/addresses/bart.person@example.com')
    email: bart.person@example.com
    http_etag: "..."
    original_email: Bart.Person@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/bart.person@example.com

But his address record can be accessed with the case-preserved version too.

    >>> dump_json(
    ...     'http://localhost:9001/3.0/addresses/Bart.Person@example.com')
    email: bart.person@example.com
    http_etag: "..."
    original_email: Bart.Person@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/bart.person@example.com

When an address has a real name associated with it, this is also available in
the REST API.

    >>> cris = user_manager.create_address('cris@example.com', 'Cris Person')
    >>> transaction.commit()
    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
    display_name: Cris Person
    email: cris@example.com
    http_etag: "..."
    original_email: cris@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/cris@example.com


Verifying
=========

When the address gets verified, this attribute is available in the REST
representation.

    >>> from mailman.utilities.datetime import now
    >>> anne.verified_on = now()
    >>> transaction.commit()
    >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com')
    email: anne@example.com
    http_etag: "..."
    original_email: anne@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/anne@example.com
    verified_on: 2005-08-01T07:49:23

Addresses can also be verified through the REST API, by POSTing to the
'verify' sub-resource.  The POST data is ignored.

    >>> dump_json('http://localhost:9001/3.0/addresses/'
    ...           'cris@example.com/verify', {})
    date: ...
    server: ...
    status: 204

Now Cris's address is verified.

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
    display_name: Cris Person
    email: cris@example.com
    http_etag: "..."
    original_email: cris@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/cris@example.com
    verified_on: 2005-08-01T07:49:23

If you should ever need to 'unverify' an address, POST to the 'unverify'
sub-resource.  Again, the POST data is ignored.

    >>> dump_json('http://localhost:9001/3.0/addresses/'
    ...           'cris@example.com/unverify', {})
    date: ...
    server: ...
    status: 204

Now Cris's address is unverified.

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
    display_name: Cris Person
    email: cris@example.com
    http_etag: "..."
    original_email: cris@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/cris@example.com


Updating
========

Each address has a ``display_name`` associated with it. This name can be
updated by PATCH'ing the address resource::

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com',
    ...           method='PATCH', data={'display_name': 'Cris P. Person'})
    date: ...
    server: ...
    status: 204

This should update the display_name associated::

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
    display_name: Cris P. Person
    email: cris@example.com
    http_etag: "..."
    original_email: cris@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/cris@example.com


The user
========

To link an address to a user, a POST request can be sent to the ``/user``
sub-resource of the address.  If the user does not exist, it will be created.

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user',
    ...           {'display_name': 'Cris X. Person'})
    content-length: 0
    content-type: application/json
    date: ...
    location: http://localhost:9001/3.0/users/1
    server: ...
    status: 201

The user is now created and the address is linked to it:

    >>> cris.user
    <User "Cris X. Person" (1) at 0x...>
    >>> cris_user = user_manager.get_user('cris@example.com')
    >>> cris_user
    <User "Cris X. Person" (1) at 0x...>
    >>> cris.user == cris_user
    True
    >>> [a.email for a in cris_user.addresses]
    ['cris@example.com']

A link to the user resource is now available as a sub-resource.

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com')
    display_name: Cris P. Person
    email: cris@example.com
    http_etag: "..."
    original_email: cris@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/cris@example.com
    user: http://localhost:9001/3.0/users/1

To prevent automatic user creation from taking place, add the `auto_create`
parameter to the POST request and set it to False.

    >>> dump_json('http://localhost:9001/3.0/addresses/anne@example.com/user',
    ...           {'display_name': 'Anne User', 'auto_create': False})
    HTTP Error 403: 403 Forbidden

A request to the `/user` sub-resource will return the linked user's
representation:

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user')
    created_on: 2005-08-01T07:49:23
    display_name: Cris X. Person
    http_etag: "..."
    is_server_owner: False
    password: ...
    self_link: http://localhost:9001/3.0/users/1
    user_id: 1

The address and the user can be unlinked by sending a DELETE request on the
`/user` resource.  The user itself is not deleted, only the link.

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user',
    ...           method='DELETE')
    date: ...
    server: ...
    status: 204
    >>> transaction.abort()
    >>> cris.user == None
    True
    >>> from uuid import UUID
    >>> user_manager.get_user_by_id(UUID(int=1))
    <User "Cris X. Person" (1) at 0x...>
    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user')
    HTTP Error 404: 404 Not Found

You can link an existing user to an address by passing the user's ID in the
POST request.
::

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user',
    ...           {'user_id': 1})
    content-length: 0
    content-type: application/json
    date: ...
    server: ...
    status: 200

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user')
    created_on: ...
    display_name: Cris X. Person
    http_etag: ...
    password: ...
    self_link: http://localhost:9001/3.0/users/1
    user_id: 1

To link an address to a different user, you can either send a DELETE request
followed by a POST request, or you can send a PUT request.
::

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user',
    ...           {'display_name': 'Cris Q Person'}, method="PUT")
    content-length: 0
    content-type: application/json
    date: ...
    location: http://localhost:9001/3.0/users/2
    server: ...
    status: 201

    >>> dump_json('http://localhost:9001/3.0/addresses/cris@example.com/user')
    created_on: ...
    display_name: Cris Q Person
    http_etag: ...
    password: ...
    self_link: http://localhost:9001/3.0/users/2
    user_id: 2


User addresses
==============

Users control addresses.  The canonical URLs for these user-controlled
addresses live in the ``/addresses`` namespace.
::

    >>> dave = user_manager.create_user('dave@example.com', 'Dave Person')
    >>> transaction.commit()
    >>> dump_json('http://localhost:9001/3.0/users/dave@example.com/addresses')
    entry 0:
        display_name: Dave Person
        email: dave@example.com
        http_etag: "..."
        original_email: dave@example.com
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.0/addresses/dave@example.com
        user: http://localhost:9001/3.0/users/3
    http_etag: "..."
    start: 0
    total_size: 1

    >>> dump_json('http://localhost:9001/3.0/addresses/dave@example.com')
    display_name: Dave Person
    email: dave@example.com
    http_etag: "..."
    original_email: dave@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/dave@example.com
    user: http://localhost:9001/3.0/users/3

A user can be associated with multiple email addresses.  You can add new
addresses to an existing user.

    >>> dump_json(
    ...     'http://localhost:9001/3.0/users/dave@example.com/addresses', {
    ...           'email': 'dave.person@example.org'
    ...           })
    content-length: 0
    content-type: application/json
    date: ...
    location: http://localhost:9001/3.0/addresses/dave.person@example.org
    server: ...
    status: 201

When you add the new address, you can give it an optional display name.

    >>> dump_json(
    ...     'http://localhost:9001/3.0/users/dave@example.com/addresses', {
    ...           'email': 'dp@example.org',
    ...           'display_name': 'Davie P',
    ...           })
    content-length: 0
    content-type: application/json
    date: ...
    location: http://localhost:9001/3.0/addresses/dp@example.org
    server: ...
    status: 201

The user controls these new addresses.

    >>> dump_json('http://localhost:9001/3.0/users/dave@example.com/addresses')
    entry 0:
        email: dave.person@example.org
        http_etag: "..."
        original_email: dave.person@example.org
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.0/addresses/dave.person@example.org
        user: http://localhost:9001/3.0/users/3
    entry 1:
        display_name: Dave Person
        email: dave@example.com
        http_etag: "..."
        original_email: dave@example.com
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.0/addresses/dave@example.com
        user: http://localhost:9001/3.0/users/3
    entry 2:
        display_name: Davie P
        email: dp@example.org
        http_etag: "..."
        original_email: dp@example.org
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.0/addresses/dp@example.org
        user: http://localhost:9001/3.0/users/3
    http_etag: "..."
    start: 0
    total_size: 3


Preferred address
=================

Each user can have a preferred address. Initially, every user's preferred
address is unset.
::

    >>> ram = user_manager.create_user('ram@example.com', 'Ram Person')
    >>> transaction.commit()
    >>> dump_json(
    ...    'http://localhost:9001/3.1/users/ram@example.com/addresses')
    entry 0:
        display_name: Ram Person
        email: ram@example.com
        http_etag: "..."
        original_email: ram@example.com
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.1/addresses/ram@example.com
        user: http://localhost:9001/3.1/users/00000000000000000000000000000004
    http_etag: "..."
    start: 0
    total_size: 1
    >>> import pdb
    >>> dump_json('http://localhost:9001/3.1/users/ram@example.com/preferred_address')
    HTTP Error 404: 404 Not Found

Setting Ram's preferred addresses requires that it first be verified:
::

    >>> dump_json('http://localhost:9001/3.1/users/ram@example.com/preferred_address',
    ...     {'email': 'ram@example.com'})
    HTTP Error 400: Ram Person <ram@example.com> must be verified before setting as primary

Verify Ram's address first:
::

    >>> addr = ram.addresses[0]
    >>> addr.verified_on = now()
    >>> transaction.commit()

Now, Ram can set his preferred address:
::

    >>> dump_json('http://localhost:9001/3.1/users/ram@example.com/preferred_address',
    ...     {'email': 'ram@example.com'})
    content-length: 0
    content-type: application/json
    date: ...
    location: http://localhost:9001/3.1/addresses/ram@example.com
    server: ...
    status: 201
    >>> dump_json('http://localhost:9001/3.1/users/ram@example.com/preferred_address')
    display_name: Ram Person
    email: ram@example.com
    http_etag: "..."
    original_email: ram@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.1/addresses/ram@example.com
    user: http://localhost:9001/3.1/users/00000000000000000000000000000004
    verified_on: 2005-08-01T07:49:23


To unset Ram's preferred address, call a ``DELETE`` on preferred address resource:
::

   >>> dump_json('http://localhost:9001/3.1/users/ram@example.com/preferred_address',
   ...     method='DELETE')
   date: ...
   server: ...
   status: 204

Memberships
===========

Addresses can be subscribed to mailing lists.  When they are, all the
membership records for that address are easily accessible via the REST API.

Elle registers several email addresses.

    >>> elle = user_manager.create_user('elle@example.com', 'Elle Person')
    >>> subscriber = list(elle.addresses)[0]
    >>> elle.register('eperson@example.com')
    <Address: eperson@example.com [not verified] at ...>
    >>> elle.register('elle.person@example.com')
    <Address: elle.person@example.com [not verified] at ...>

Elle subscribes to two mailing lists with one of her addresses.
::

    >>> from mailman.app.lifecycle import create_list
    >>> ant = create_list('ant@example.com')
    >>> bee = create_list('bee@example.com')
    >>> ant.subscribe(subscriber)
    <Member: Elle Person <elle@example.com> on ant@example.com
             as MemberRole.member>
    >>> bee.subscribe(subscriber)
    <Member: Elle Person <elle@example.com> on bee@example.com
             as MemberRole.member>
    >>> transaction.commit()

Elle can get her memberships for each of her email addresses.
::

    >>> dump_json('http://localhost:9001/3.0/addresses/'
    ...           'elle@example.com/memberships')
    entry 0:
        address: http://localhost:9001/3.0/addresses/elle@example.com
        bounce_score: 0
        delivery_mode: regular
        display_name: Elle Person
        email: elle@example.com
        http_etag: "..."
        last_warning_sent: 0001-01-01T00:00:00
        list_id: ant.example.com
        member_id: 1
        role: member
        self_link: http://localhost:9001/3.0/members/1
        subscription_mode: as_address
        total_warnings_sent: 0
        user: http://localhost:9001/3.0/users/5
    entry 1:
        address: http://localhost:9001/3.0/addresses/elle@example.com
        bounce_score: 0
        delivery_mode: regular
        display_name: Elle Person
        email: elle@example.com
        http_etag: "..."
        last_warning_sent: 0001-01-01T00:00:00
        list_id: bee.example.com
        member_id: 2
        role: member
        self_link: http://localhost:9001/3.0/members/2
        subscription_mode: as_address
        total_warnings_sent: 0
        user: http://localhost:9001/3.0/users/5
    http_etag: "..."
    start: 0
    total_size: 2

    >>> dump_json('http://localhost:9001/3.0/addresses/'
    ...           'eperson@example.com/memberships')
    http_etag: "..."
    start: 0
    total_size: 0

When Elle subscribes to the `bee` list again with a different address, this
does not show up in the list of memberships for his other address.
::

    >>> subscriber = user_manager.get_address('eperson@example.com')
    >>> bee.subscribe(subscriber)
    <Member: eperson@example.com on bee@example.com as MemberRole.member>
    >>> transaction.commit()

    >>> dump_json('http://localhost:9001/3.0/addresses/'
    ...           'elle@example.com/memberships')
    entry 0:
        address: http://localhost:9001/3.0/addresses/elle@example.com
        bounce_score: 0
        delivery_mode: regular
        display_name: Elle Person
        email: elle@example.com
        http_etag: "..."
        last_warning_sent: 0001-01-01T00:00:00
        list_id: ant.example.com
        member_id: 1
        role: member
        self_link: http://localhost:9001/3.0/members/1
        subscription_mode: as_address
        total_warnings_sent: 0
        user: http://localhost:9001/3.0/users/5
    entry 1:
        address: http://localhost:9001/3.0/addresses/elle@example.com
        bounce_score: 0
        delivery_mode: regular
        display_name: Elle Person
        email: elle@example.com
        http_etag: "..."
        last_warning_sent: 0001-01-01T00:00:00
        list_id: bee.example.com
        member_id: 2
        role: member
        self_link: http://localhost:9001/3.0/members/2
        subscription_mode: as_address
        total_warnings_sent: 0
        user: http://localhost:9001/3.0/users/5
    http_etag: "..."
    start: 0
    total_size: 2

    >>> dump_json('http://localhost:9001/3.0/addresses/'
    ...           'eperson@example.com/memberships')
    entry 0:
        address: http://localhost:9001/3.0/addresses/eperson@example.com
        bounce_score: 0
        delivery_mode: regular
        display_name: Elle Person
        email: eperson@example.com
        http_etag: "..."
        last_warning_sent: 0001-01-01T00:00:00
        list_id: bee.example.com
        member_id: 3
        role: member
        self_link: http://localhost:9001/3.0/members/3
        subscription_mode: as_address
        total_warnings_sent: 0
        user: http://localhost:9001/3.0/users/5
    http_etag: "..."
    start: 0
    total_size: 1




Deleting
========

Addresses can be deleted via the REST API.
::

    >>> fred = user_manager.create_address('fred@example.com', 'Fred Person')
    >>> transaction.commit()
    >>> dump_json('http://localhost:9001/3.0/addresses/fred@example.com')
    display_name: Fred Person
    email: fred@example.com
    http_etag: "..."
    original_email: fred@example.com
    registered_on: 2005-08-01T07:49:23
    self_link: http://localhost:9001/3.0/addresses/fred@example.com

    >>> dump_json('http://localhost:9001/3.0/addresses/fred@example.com',
    ...     method='DELETE')
    date: ...
    server: ...
    status: 204
    >>> transaction.abort()

    >>> print(user_manager.get_address('fred@example.com'))
    None

If an address is linked to a user, deleting the address does not delete the
user, it just unlinks it.
::

    >>> gwen = user_manager.create_user('gwen@example.com', 'Gwen Person')
    >>> transaction.commit()
    >>> dump_json('http://localhost:9001/3.0/users/6/addresses')
    entry 0:
        display_name: Gwen Person
        email: gwen@example.com
        http_etag: "..."
        original_email: gwen@example.com
        registered_on: 2005-08-01T07:49:23
        self_link: http://localhost:9001/3.0/addresses/gwen@example.com
        user: http://localhost:9001/3.0/users/6
    http_etag: "..."
    start: 0
    total_size: 1


    >>> dump_json('http://localhost:9001/3.0/addresses/gwen@example.com',
    ...     method='DELETE')
    date: ...
    server: ...
    status: 204

    >>> dump_json('http://localhost:9001/3.0/users/6/addresses')
    http_etag: "..."
    start: 0
    total_size: 0
