Completed
Push — main ( 79af2c...5d8ea8 )
by Jochen
03:35
created

byceps.services.consent.consent_service   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 114
Duplicated Lines 0 %

Test Coverage

Coverage 59.46%

Importance

Changes 0
Metric Value
eloc 61
dl 0
loc 114
ccs 22
cts 37
cp 0.5946
rs 10
c 0
b 0
f 0
wmc 13

8 Functions

Rating   Name   Duplication   Size   Complexity  
A count_consents_by_subject() 0 12 1
A consent_to_subject() 0 7 1
A build_consent() 0 5 1
A get_consents_by_user() 0 5 1
A consent_to_subjects() 0 16 2
A get_unconsented_subject_ids() 0 11 3
A has_user_consented_to_all_subjects() 0 9 3
A has_user_consented_to_subject() 0 13 1
1
"""
2
byceps.services.consent.consent_service
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2020 Jochen Kupperschmidt
6
:License: Modified BSD, see LICENSE for details.
7
"""
8
9 1
from datetime import datetime
10 1
from typing import Dict, Iterable, Sequence, Set
11
12 1
from ...database import db
13 1
from ...typing import UserID
14
15 1
from ..verification_token.models import Token
16
17 1
from .models.consent import Consent as DbConsent
18 1
from .models.subject import Subject as DbSubject
19 1
from .transfer.models import SubjectID
20
21
22 1
def build_consent(
23
    user_id: UserID, subject_id: SubjectID, expressed_at: datetime
24
) -> DbConsent:
25
    """Create user's consent to that subject."""
26 1
    return DbConsent(user_id, subject_id, expressed_at)
27
28
29 1
def consent_to_subject(
30
    subject_id: SubjectID, expressed_at: datetime, verification_token: Token
31
) -> None:
32
    """Store the user's consent to that subject, and invalidate the
33
    verification token.
34
    """
35
    consent_to_subjects([subject_id], expressed_at, verification_token)
36
37
38 1
def consent_to_subjects(
39
    subject_ids: Iterable[SubjectID],
40
    expressed_at: datetime,
41
    verification_token: Token,
42
) -> None:
43
    """Store the user's consent to these subjects, and invalidate the
44
    verification token.
45
    """
46
    user_id = verification_token.user_id
47
    db.session.delete(verification_token)
48
49
    for subject_id in subject_ids:
50
        consent = build_consent(user_id, subject_id, expressed_at)
51
        db.session.add(consent)
52
53
    db.session.commit()
54
55
56 1
def count_consents_by_subject() -> Dict[str, int]:
57
    """Return the number of given consents per subject."""
58 1
    rows = db.session \
59
        .query(
60
            DbSubject.name,
61
            db.func.count(DbConsent.user_id)
62
        ) \
63
        .outerjoin(DbConsent) \
64
        .group_by(DbSubject.name) \
65
        .all()
66
67 1
    return dict(rows)
68
69
70 1
def get_consents_by_user(user_id: UserID) -> Sequence[DbConsent]:
71
    """Return the consents the user submitted."""
72
    return DbConsent.query \
73
        .filter_by(user_id=user_id) \
74
        .all()
75
76
77 1
def get_unconsented_subject_ids(
78
    user_id: UserID, required_subject_ids: Set[SubjectID]
79
) -> Set[SubjectID]:
80
    """Return the IDs of the subjects the user has not consented to."""
81
    unconsented_subject_ids = set()
82
83
    for subject_id in required_subject_ids:
84
        if not has_user_consented_to_subject(user_id, subject_id):
85
            unconsented_subject_ids.add(subject_id)
86
87
    return unconsented_subject_ids
88
89
90 1
def has_user_consented_to_all_subjects(
91
    user_id: UserID, subject_ids: Set[SubjectID]
92
) -> bool:
93
    """Return `True` if the user has consented to all given subjects."""
94 1
    for subject_id in subject_ids:
95
        if not has_user_consented_to_subject(user_id, subject_id):
96
            return False
97
98 1
    return True
99
100
101 1
def has_user_consented_to_subject(
102
    user_id: UserID, subject_id: SubjectID
103
) -> bool:
104
    """Determine if the user has consented to the subject."""
105 1
    return db.session \
106
        .query(
107
            db.session
108
                .query(DbConsent)
109
                .filter_by(user_id=user_id)
110
                .filter_by(subject_id=subject_id)
111
                .exists()
112
        ) \
113
        .scalar()
114