コード例 #1
0
def fetch_use_case_pools():
    """Retrieve a list of the available pools of anonymized user examples."""
    use_case_pools = use_case_pb2.UseCasePools()
    use_case_pool_dicts = flask.current_app.config[
        'DATABASE'].use_case.aggregate([
            {
                '$group': {
                    '_id': '$poolName',
                    'useCaseCount': {
                        '$sum': 1
                    },
                    'evaluatedUseCaseCount': {
                        '$sum': {
                            '$cond': [{
                                '$gt': ['$evaluation', None]
                            }, 1, 0]
                        }
                    },
                    'lastUserRegisteredAt': {
                        '$max': '$userData.registeredAt'
                    },
                }
            },
            {
                '$sort': {
                    'lastUserRegisteredAt': -1
                }
            },
        ])
    for use_case_pool_dict in use_case_pool_dicts:
        use_case_pool_proto = use_case_pools.use_case_pools.add()
        use_case_pool_proto.name = use_case_pool_dict.pop('_id')
        proto.parse_from_mongo(use_case_pool_dict, use_case_pool_proto)

    return use_case_pools
コード例 #2
0
def main(user_db, dry_run=True):
    """Fix projects with very old field values."""
    if dry_run:
        print('Running in dry mode, no changes will be pushed to MongoDB.')
    # TODO(pascal): Also run this fix on other projects with old fields:
    #  - the ones with kind == FIND_JOB
    #  - the ones with no jobSearchStartedAt nor jobSearchHasNotStarted
    #  - the ones with actionsGeneratedAt
    users_to_fix = user_db.find({
        'projects.diplomaFulfillmentEstimate': {'$exists': True},
        'projects.trainingFulfillmentEstimate': {'$exists': False},
    })
    user_count = 0
    for user_dict in users_to_fix:
        user_id = user_dict.pop('_id')
        user = user_pb2.User()
        proto.parse_from_mongo(user_dict, user)

        updated = False
        for project in user.projects:
            if not fix_project(project, user.profile):
                continue

            updated = True
            if dry_run:
                print('Would change project for', user_id, project.project_id)
            else:
                user_db.update_one(
                    {'_id': user_id, 'projects.projectId': project.project_id},
                    {'$set': {'projects.$': json_format.MessageToDict(project)}},
                )

        if updated:
            user_count += 1
    print('{} users updated'.format(user_count))
コード例 #3
0
    def _get_seasonal_departements(self, project):
        """Compute departements that propose seasonal jobs."""
        top_departements = seasonal_jobbing_pb2.MonthlySeasonalJobbingStats()

        # TODO(guillaume): Cache this to increase speed.
        proto.parse_from_mongo(
            project.database.seasonal_jobbing.find_one(
                {'_id': project.now.month}), top_departements)

        for departement in top_departements.departement_stats:
            # TODO(guillaume): If we don't use deeper jobgroups by october 1st 2017, trim the db.
            del departement.job_groups[6:]

            try:
                departement.departement_in_name = geo.get_in_a_departement_text(
                    departement.departement_id)
            except KeyError:
                logging.exception(
                    'Prefix or name not found for departement: %s',
                    departement.departement_id)
                continue

        for i, departement in enumerate(
                top_departements.departement_stats[::-1]):
            if not departement.departement_in_name:
                del top_departements.departement_stats[i]

        return top_departements or []
コード例 #4
0
    def send_reset_password_token(self, email):
        """Sends an email to user with a reset token so that they can reset their password."""
        user_dict = self._db.user.find_one({'profile.email': email})
        if not user_dict:
            flask.abort(403, "Nous n'avons pas d'utilisateur avec cet email : %s" % email)
        user_auth_dict = self._db.user_auth.find_one({'_id': user_dict['_id']})
        if not user_auth_dict or not user_auth_dict.get('hashedPassword'):
            flask.abort(
                403, 'Utilisez Facebook ou Google pour vous connecter, comme la première fois.')

        hashed_old_password = user_auth_dict.get('hashedPassword')
        auth_token = _timestamped_hash(
            int(time.time()), email + str(user_dict['_id']) + hashed_old_password)

        user_profile = user_pb2.UserProfile()
        proto.parse_from_mongo(user_dict.get('profile'), user_profile)

        reset_link = parse.urljoin(flask.request.url, '/?' + parse.urlencode({
            'email': email,
            'resetToken': auth_token}))
        template_vars = {
            'resetLink': reset_link,
            'firstName': user_profile.name,
        }
        result = mail.send_template(
            '71254', user_profile, template_vars, monitoring_category='reset_password')
        if result.status_code != 200:
            logging.error('Failed to send an email with MailJet:\n %s', result.text)
            flask.abort(result.status_code)
コード例 #5
0
ファイル: advisor.py プロジェクト: tkoutangni/bob-emploi
def select_advice_for_email(user, weekday, database):
    """Select an advice to promote in a follow-up email."""
    if not user.projects:
        return None

    project = user.projects[0]
    if not project.advices:
        return None

    easy_advice_modules = _easy_advice_modules(database)
    history = advisor_pb2.EmailHistory()
    history_dict = database.email_history.find_one(
        {'_id': objectid.ObjectId(user.user_id)})
    proto.parse_from_mongo(history_dict, history)

    today = now.get()
    last_monday = today - datetime.timedelta(days=today.weekday())

    def _score_advice(priority_and_advice):
        """Score piece of advice to compare to others, the higher the better."""
        priority, advice = priority_and_advice
        score = 0

        # Enforce priority advice on Mondays (between -10 and +10).
        if weekday == user_pb2.MONDAY:
            priority_score = (advice.score
                              or 5) - 10 * priority / len(project.advices)
            score += priority_score

        # Enforce easy advice on Fridays (between +0 and +1).
        if weekday == user_pb2.FRIDAY and advice.advice_id in easy_advice_modules:
            score += 1

        random_factor = (advice.score or 5) / 10

        last_sent = history.advice_modules[advice.advice_id].ToDatetime()
        last_sent_monday = last_sent - datetime.timedelta(
            days=last_sent.weekday())

        # Do not send the same advice in the same week (+0 or -20).
        if last_sent_monday >= last_monday:
            score -= 20
        # Reduce the random boost if advice was sent in past weeks (*0.2 the
        # week just before, *0.45 the week before that, *0.585, *0.669, …) .
        else:
            num_weeks_since_last_sent = (last_monday -
                                         last_sent_monday).days / 7
            random_factor *= .2**(1 / num_weeks_since_last_sent)

        # Randomize pieces of advice with the same score (between +0 and +1).
        score += random.random() * random_factor

        return score

    candidates = sorted(enumerate(project.advices),
                        key=_score_advice,
                        reverse=True)
    return next(advice for priority, advice in candidates)
コード例 #6
0
ファイル: advisor.py プロジェクト: tkoutangni/bob-emploi
def select_tips_for_email(user,
                          project,
                          piece_of_advice,
                          database,
                          num_tips=3):
    """Select tips to promote an advice in a follow-up email."""
    all_templates = list_all_tips(user,
                                  project,
                                  piece_of_advice,
                                  database,
                                  filter_tip=lambda t: t.is_ready_for_email)

    # TODO(pascal): Factorize with above.
    history = advisor_pb2.EmailHistory()
    history_dict = database.email_history.find_one(
        {'_id': objectid.ObjectId(user.user_id)})
    proto.parse_from_mongo(history_dict, history)

    today = now.get()

    def _score_tip_template(tip_template):
        """Score tip template to compare to others, the higher the better."""
        score = 0

        last_sent = history.tips[tip_template.action_template_id].ToDatetime()
        # Score higher the tips that have not been sent for longer time.
        score += (today - last_sent).days

        # Randomize tips with the same score.
        score += random.random()

        return score

    selected_templates = sorted(all_templates,
                                key=_score_tip_template)[-num_tips:]
    if not selected_templates:
        return None

    # Instantiate actual tips.
    selected_tips = [
        action.instantiate(action_pb2.Action(),
                           user,
                           project,
                           template,
                           set(),
                           database,
                           None,
                           for_email=True) for template in selected_templates
    ]

    # Replicate tips if we do not have enough.
    while len(selected_tips) < num_tips:
        selected_tips.extend(selected_tips)

    return selected_tips[:num_tips]
コード例 #7
0
def fetch_use_cases(pool_name):
    """Retrieve a list of anonymized user examples from one pool."""
    use_cases = use_case_pb2.UseCases()
    use_case_dicts = flask.current_app.config['DATABASE'].use_case\
        .find({'poolName': pool_name}).sort('indexInPool', 1)
    for use_case_dict in use_case_dicts:
        use_case_proto = use_cases.use_cases.add()
        use_case_proto.use_case_id = use_case_dict.pop('_id')
        proto.parse_from_mongo(use_case_dict, use_case_proto)

    return use_cases
コード例 #8
0
ファイル: auth.py プロジェクト: tkoutangni/bob-emploi
    def _google_authenticate(self, token_id):
        try:
            id_info = client.verify_id_token(token_id, GOOGLE_SSO_CLIENT_ID)
        except crypt.AppIdentityError as error:
            flask.abort(401, "Mauvais jeton d'authentification : %s" % error)
        if id_info.get('iss') not in _GOOGLE_SSO_ISSUERS:
            flask.abort(
                401, "Fournisseur d'authentification invalide : %s." %
                id_info.get('iss', '<none>'))

        response = user_pb2.AuthResponse()
        user_dict = self._db.user.find_one({'googleId': id_info['sub']})
        user_id = str(user_dict['_id']) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
        else:
            self._assert_user_not_existing(id_info['email'])
            response.authenticated_user.profile.email = id_info['email']
            response.authenticated_user.profile.picture_url = id_info.get(
                'picture', '')
            response.authenticated_user.google_id = id_info['sub']
            self._save_new_user(response.authenticated_user)
            response.is_new_user = True

        return response
コード例 #9
0
def _get_user_data(user_id):
    """Load user data from DB."""
    user_dict = _DB.user.find_one({'_id': _safe_object_id(user_id)})
    user_proto = user_pb2.User()
    if not proto.parse_from_mongo(user_dict, user_proto):
        # Switch to raising an error if you move this function in a lib.
        flask.abort(404, 'Utilisateur "%s" inconnu.' % user_id)

    _populate_feature_flags(user_proto)

    for project in user_proto.projects:
        # TODO(pascal): Update existing users and get rid of diploma_fulfillment_estimate.
        if not project.training_fulfillment_estimate and project.diploma_fulfillment_estimate:
            project.training_fulfillment_estimate = TRAINING_ESTIMATION.get(
                project.diploma_fulfillment_estimate,
                project_pb2.UNKNOWN_TRAINING_FULFILLMENT)
        # TODO(pascal): Update existing users and get rid of FIND_JOB.
        if project.kind == project_pb2.FIND_JOB:
            if user_proto.profile.situation == user_pb2.LOST_QUIT:
                project.kind = project_pb2.FIND_A_NEW_JOB
            elif user_proto.profile.situation == user_pb2.FIRST_TIME:
                project.kind = project_pb2.FIND_A_FIRST_JOB
            else:
                project.kind = project_pb2.FIND_ANOTHER_JOB

        project.ClearField('diploma_fulfillment_estimate')

    # TODO(pascal): Remove the fields completely after this has been live for a
    # week.
    user_proto.profile.ClearField('city')
    user_proto.profile.ClearField('latest_job')
    user_proto.profile.ClearField('situation')

    return user_proto
コード例 #10
0
ファイル: auth.py プロジェクト: tkoutangni/bob-emploi
    def _facebook_authenticate(self, signed_request):
        try:
            [encoded_signature, payload] = signed_request.split('.')
            data = json.loads(_base64_url_decode(payload).decode('utf-8'))
            actual_signature = _base64_url_decode(encoded_signature)
        except ValueError as error:
            flask.abort(422, error)
        for required_field in ('algorithm', 'user_id'):
            if not data.get(required_field):
                flask.abort(
                    422,
                    'Le champs %s est requis : %s' % (required_field, data))
        if data['algorithm'].lower() != 'hmac-sha256':
            flask.abort(
                422,
                'Algorithme d\'encryption inconnu "%s"' % data['algorithm'])

        expected_signature = hmac.new(_FACEBOOK_SECRET,
                                      payload.encode('utf-8'),
                                      hashlib.sha256).digest()
        if expected_signature != actual_signature:
            flask.abort(403, 'Mauvaise signature')

        response = user_pb2.AuthResponse()
        user_dict = self._db.user.find_one({'facebookId': data['user_id']})
        user_id = str(user_dict['_id']) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
        else:
            response.authenticated_user.facebook_id = data['user_id']
            self._save_new_user(response.authenticated_user)
            response.is_new_user = True

        return response
コード例 #11
0
def main():
    """Send an email to a user to tell them to focus on the network."""
    campaign_id = 'focus-network'
    template_id = '205970'
    email_count = 0
    selected_users = _DB.user.find({
        'registeredAt': {
            '$gt': '2017-04-01',
            '$lt': '2017-07-10',
        },
        'projects.networkEstimate': 1,
    })
    for user_dict in selected_users:
        user_id = user_dict.pop('_id')
        user = user_pb2.User()
        proto.parse_from_mongo(user_dict, user)

        if any(email.campaign_id == campaign_id for email in user.emails_sent):
            # We already sent the email to that person.
            continue

        template_vars = network_vars(user)
        if not template_vars:
            continue

        req = mail.send_template(template_id, user.profile, template_vars)
        print('Email sent to %s' % user.profile.email)

        if req.status_code != 200:
            logging.warning('Error while sending an email: %d',
                            req.status_code)
            continue

        email_sent = user.emails_sent.add()
        email_sent.sent_at.GetCurrentTime()
        email_sent.mailjet_template = template_id
        email_sent.campaign_id = campaign_id
        _DB.user.update_one({'_id': user_id}, {
            '$set': {
                'emailsSent':
                json_format.MessageToDict(user).get('emailsSent', []),
            }
        })
        email_count += 1

    return email_count
コード例 #12
0
ファイル: server.py プロジェクト: aurelien-ideal/bob-emploi
def get_dashboard_export(dashboard_export_id):
    """Retrieve an export of the user's current dashboard."""
    dashboard_export_json = _DB.dashboard_exports.find_one(
        {'_id': _safe_object_id(dashboard_export_id)})
    dashboard_export = export_pb2.DashboardExport()
    if not proto.parse_from_mongo(dashboard_export_json, dashboard_export):
        flask.abort(404, 'Export "%s" introuvable.' % dashboard_export_id)
    dashboard_export.dashboard_export_id = dashboard_export_id
    return dashboard_export
コード例 #13
0
ファイル: proto_test.py プロジェクト: mehdinassih/bob-emploi
 def test_unknown_field(self):
     """Unknown fields do not make the function choke."""
     job_group = job_pb2.JobGroup()
     self.assertTrue(
         proto.parse_from_mongo({
             'romeId': 'A123',
             'unknownField': 14
         }, job_group))
     self.assertEqual('A123', job_group.rome_id)
コード例 #14
0
ファイル: server.py プロジェクト: aurelien-ideal/bob-emploi
def get_usage_stats():
    """Get stats of the app usage."""
    now_utc = now.get().astimezone(datetime.timezone.utc)
    start_of_second = now_utc.replace(microsecond=0, tzinfo=None)
    last_week = start_of_second - datetime.timedelta(days=7)
    yesterday = start_of_second - datetime.timedelta(days=1)

    # Compute daily scores count.
    daily_scores_count = collections.defaultdict(int)
    last_day_users = _DB.user.find(
        {
            'registeredAt': {
                '$gt': _datetime_to_json_string(yesterday),
                '$lte': _datetime_to_json_string(start_of_second),
            },
        },
        {
            'profile.email': 1,
            'projects': 1,
            'registeredAt': 1,
        },
    )
    for user_dict in last_day_users:
        user_proto = user_pb2.User()
        proto.parse_from_mongo(user_dict, user_proto)
        if _is_test_user(user_proto):
            continue
        for project in user_proto.projects:
            if project.feedback.score:
                daily_scores_count[project.feedback.score] += 1

    # Compute weekly user count.
    weekly_new_user_count = _DB.user.find({
        'registeredAt': {
            '$gt': _datetime_to_json_string(last_week),
            '$lte': _datetime_to_json_string(start_of_second),
        }
    }).count()

    return stats_pb2.UsersCount(
        total_user_count=_DB.user.count(),
        weekly_new_user_count=weekly_new_user_count,
        daily_scores_count=daily_scores_count,
    )
コード例 #15
0
def main(user_db, dry_run=True):
    """Fix projects with very old field values."""
    if dry_run:
        print('Running in dry mode, no changes will be pushed to MongoDB.')
    users_to_fix = user_db.find({
        'employment_status': {
            '$elemMatch': {
                'createdAt': {
                    '$lt': '2017-10-12'
                },
                'seeking': {
                    '$exists': False
                }
            }
        },
    })
    user_count = 0
    for user_dict in users_to_fix:
        user_id = user_dict.pop('_id')
        user = user_pb2.User()
        proto.parse_from_mongo(user_dict, user)

        updated = False
        for pos, status in enumerate(user.employment_status):
            if status.seeking:
                continue
            updated = True
            if dry_run:
                print('Would change status for', user_id, pos)
            else:
                status.seeking = user_pb2.STOP_SEEKING
                user_db.update_one(
                    {'_id': user_id},
                    {
                        '$set': {
                            'employment_status.{}'.format(pos):
                            json_format.MessageToDict(status)
                        }
                    },
                )

        if updated:
            user_count += 1
    print('{} users updated'.format(user_count))
コード例 #16
0
def _update_email_sent_status(email_sent_dict, email_address):
    email_sent = user_pb2.EmailSent()
    proto.parse_from_mongo(email_sent_dict, email_sent)
    if email_sent.status != user_pb2.EMAIL_SENT_UNKNOWN:
        # TODO(pascal): Check the status again if too old.
        return email_sent_dict

    message = _find_message(email_sent, email_address)
    if not message:
        logging.warning('Could not find a message in MailJet.')
        return email_sent_dict

    email_sent.mailjet_message_id = message.get('ID',
                                                email_sent.mailjet_message_id)
    email_sent.last_status_checked_at.GetCurrentTime()
    email_sent.last_status_checked_at.nanos = 0
    email_sent.status = user_pb2.EmailSentStatus.Value('EMAIL_SENT_{}'.format(
        message.get('Status', 'unknown').upper()))
    return json_format.MessageToDict(email_sent)
コード例 #17
0
    def list_nearby_cities(self, project):
        """Compute and store all interesting cities that are not too close and not too far.

        Those cities will be used by the Commute advice.
        """
        job_group = project.details.target_job.job_group.rome_id

        all_cities = commute_pb2.HiringCities()
        proto.parse_from_mongo(
            project.database.hiring_cities.find_one({'_id': job_group}),
            all_cities)
        interesting_cities_for_rome = all_cities.hiring_cities

        if not interesting_cities_for_rome:
            return []

        target_city = geo_pb2.FrenchCity()
        mongo_city = project.database.cities.find_one(
            {'_id': project.details.mobility.city.city_id})
        if not mongo_city:
            return []
        proto.parse_from_mongo(mongo_city, target_city)

        commuting_cities = list(
            _get_commuting_cities(interesting_cities_for_rome, target_city))

        obvious_cities = [
            city for city in commuting_cities
            if city.distance_km < _MIN_CITY_DISTANCE
        ]

        interesting_cities = [
            city for city in commuting_cities
            if city.distance_km >= _MIN_CITY_DISTANCE
        ]

        # If there is only one city nearby and no obvious city, the nearby city becomes obvious, so
        # we do not recommend it.
        if len(interesting_cities) == 1 and not obvious_cities:
            return []

        return interesting_cities
コード例 #18
0
 def load_set(cls, filename):
     """Load a set of personas from a JSON file."""
     with open(filename) as personas_file:
         personas_json = json.load(personas_file)
     personas = {}
     for name, blob in personas_json.items():
         user_profile = user_pb2.UserProfile()
         assert proto.parse_from_mongo(blob['user'], user_profile)
         features_enabled = user_pb2.Features()
         if 'featuresEnabled' in blob:
             assert proto.parse_from_mongo(blob['featuresEnabled'],
                                           features_enabled)
         project = project_pb2.Project()
         assert proto.parse_from_mongo(blob['project'], project)
         assert name not in personas
         personas[name] = cls(name,
                              user_profile=user_profile,
                              project=project,
                              features_enabled=features_enabled)
     return personas
コード例 #19
0
def user_to_use_case(user, pool_name, index_in_pool):
    """Extracts a use case from a real user."""
    use_case = use_case_pb2.UseCase()
    if not proto.parse_from_mongo(user, use_case.user_data):
        return None
    use_case.title = next((p.title for p in use_case.user_data.projects), '')
    anonymize_proto(use_case.user_data, field_usages_to_clear={
        options_pb2.PERSONAL_IDENTIFIER, options_pb2.APP_ONLY, options_pb2.ALGORITHM_RESULT,
    })
    use_case.pool_name = pool_name
    use_case.index_in_pool = index_in_pool
    use_case.use_case_id = '{}_{:02x}'.format(pool_name, index_in_pool)
    return use_case
コード例 #20
0
ファイル: proto_test.py プロジェクト: mehdinassih/bob-emploi
 def test_weird_objects(self, mock_warning):
     """Raises a TypeError when an object is not of the right type."""
     job_group = job_pb2.JobGroup()
     self.assertFalse(proto.parse_from_mongo({'romeId': 123}, job_group))
     mock_warning.assert_called_once()
     self.assertEqual(
         'Error %s while parsing a JSON dict for proto type %s:\n%s',
         mock_warning.call_args[0][0])
     self.assertEqual(
         'Failed to parse romeId field: expected string or bytes-like object.',
         str(mock_warning.call_args[0][1]))
     self.assertEqual('JobGroup', str(mock_warning.call_args[0][2]))
     self.assertEqual("{'romeId': 123}", str(mock_warning.call_args[0][3]))
コード例 #21
0
    def volunteering_missions(self, project):
        """Return a list of volunteering mission close to the project."""
        departement_id = project.details.mobility.city.departement_id

        # Get data from MongoDB.
        volunteering_missions_dict = collections.defaultdict(
            association_pb2.VolunteeringMissions)
        collection = project.database.volunteering_missions
        for record in collection.find({'_id': {'$in': [departement_id, '']}}):
            record_id = record.pop('_id')
            proto.parse_from_mongo(record,
                                   volunteering_missions_dict[record_id])

        # TODO(pascal): First get missions from target city if any.

        # Merge data.
        project_missions = association_pb2.VolunteeringMissions()
        for scope in [departement_id, '']:
            for mission in volunteering_missions_dict[scope].missions:
                mission.is_available_everywhere = not scope
                project_missions.missions.add().CopyFrom(mission)

        return project_missions
コード例 #22
0
ファイル: server.py プロジェクト: aurelien-ideal/bob-emploi
def _get_user_data(user_id):
    """Load user data from DB."""
    user_dict = _DB.user.find_one({'_id': _safe_object_id(user_id)})
    user_proto = user_pb2.User()
    if not proto.parse_from_mongo(user_dict, user_proto):
        # Switch to raising an error if you move this function in a lib.
        flask.abort(404, 'Utilisateur "%s" inconnu.' % user_id)

    _populate_feature_flags(user_proto)

    for project in user_proto.projects:
        # TODO(pascal): Update existing users and get rid of diploma_fulfillment_estimate.
        if not project.training_fulfillment_estimate and project.diploma_fulfillment_estimate:
            project.training_fulfillment_estimate = TRAINING_ESTIMATION.get(
                project.diploma_fulfillment_estimate,
                project_pb2.UNKNOWN_TRAINING_FULFILLMENT)
        # TODO(pascal): Update existing users and get rid of FIND_JOB.
        if project.kind == project_pb2.FIND_JOB:
            if user_proto.profile.situation == user_pb2.LOST_QUIT:
                project.kind = project_pb2.FIND_A_NEW_JOB
            elif user_proto.profile.situation == user_pb2.FIRST_TIME:
                project.kind = project_pb2.FIND_A_FIRST_JOB
            else:
                project.kind = project_pb2.FIND_ANOTHER_JOB

        # TODO(pascal): Update existing users and get rid of job_search_length_months.
        if not (project.job_search_started_at.seconds or project.job_search_has_not_started) \
                and project.job_search_length_months:
            if project.job_search_length_months < 0:
                project.job_search_has_not_started = True
            else:
                job_search_length_days = 30.5 * project.job_search_length_months
                job_search_length_duration = datetime.timedelta(
                    days=job_search_length_days)
                project.job_search_started_at.FromDatetime(
                    project.created_at.ToDatetime() -
                    job_search_length_duration)
                project.job_search_started_at.nanos = 0

        project.ClearField('diploma_fulfillment_estimate')
        project.ClearField('actions_generated_at')

    # TODO(pascal): Remove the fields completely after this has been live for a
    # week.
    user_proto.profile.ClearField('city')
    user_proto.profile.ClearField('latest_job')
    user_proto.profile.ClearField('situation')

    return user_proto
コード例 #23
0
ファイル: server.py プロジェクト: aurelien-ideal/bob-emploi
def _populate_job_stats_dict(local_job_stats, city):
    local_stats_ids = dict(
        ('%s:%s' % (city.departement_id, job_group_id), job_group_id)
        for job_group_id in local_job_stats)

    # Import most stats from local_diagnosis.
    local_stats = _DB.local_diagnosis.find(
        {'_id': {
            '$in': list(local_stats_ids)
        }})
    for job_group_local_stats in local_stats:
        job_group_id = local_stats_ids[job_group_local_stats['_id']]
        proto.parse_from_mongo(job_group_local_stats,
                               local_job_stats[job_group_id])

    # Enrich with the # of job offers.
    job_group_offers_counts = _DB.recent_job_offers.find(
        {'_id': {
            '$in': list(local_stats_ids)
        }})
    for job_group_offers_count in job_group_offers_counts:
        job_group_id = local_stats_ids[job_group_offers_count['_id']]
        proto.parse_from_mongo(job_group_offers_count,
                               local_job_stats[job_group_id])
コード例 #24
0
ファイル: mail_advice.py プロジェクト: tkoutangni/bob-emploi
def main(database, base_url, now):
    """Send an email with a selected advice to a list of users."""
    # Week day as a user_pb2.WeekDay value.
    weekday = now.weekday() + 1
    query = {
        'profile.emailDays': user_pb2.WeekDay.Name(weekday),
        'projects.advices.score': {'$gt': 0},
        'featuresEnabled.advisorEmail': 'ACTIVE',
    }
    count = 0
    cool_down_time_beginning = now - _COOL_DOWN_TIME
    user_iterator = database.user.find(query)
    errors = []
    for user_in_db in _break_on_signal([signal.SIGTERM], user_iterator):
        user = user_pb2.User()
        user_id = user_in_db['_id']
        if not proto.parse_from_mongo(user_in_db, user):
            # Skip silently (the proto library already logs the error).
            continue
        if user.last_email_sent_at.ToDatetime() > cool_down_time_beginning:
            # Skip silently.
            continue

        if _deactivate_if_never_opened(user_id, user, database.user):
            continue

        try:
            result = send_email_to_user(user, base_url, weekday, database)
        except (IOError, json_format.ParseError) as err:
            errors.append('%s - %s' % (err, user_id))
            logging.error(err)
            continue

        if not result:
            continue

        if not DRY_RUN:
            database.user.update_one(
                {'_id': user_id},
                {'$set': {'lastEmailSentAt': now}})

        count += 1

    _send_reports(count, errors, weekday)
コード例 #25
0
    def _google_authenticate(self, token_id):
        id_info = decode_google_id_token(token_id)
        response = user_pb2.AuthResponse()
        user_dict = self._db.user.find_one({'googleId': id_info['sub']})
        user_id = str(user_dict['_id']) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
        else:
            self._assert_user_not_existing(id_info['email'])
            response.authenticated_user.profile.email = id_info['email']
            response.authenticated_user.profile.picture_url = id_info.get('picture', '')
            response.authenticated_user.google_id = id_info['sub']
            self._save_new_user(response.authenticated_user)
            response.is_new_user = True

        # TODO(benoit): Check token provide enough guarantee for this use case.
        response.auth_token = create_token(response.authenticated_user.user_id, 'auth')

        return response
コード例 #26
0
ファイル: server.py プロジェクト: aurelien-ideal/bob-emploi
def redirect_eterritoire(city_id):
    """Redirect to the e-Territoire page for a city."""
    link = association_pb2.SimpleLink()
    proto.parse_from_mongo(_DB.eterritoire_links.find_one({'_id': city_id}),
                           link)
    return flask.redirect('http://www.eterritoire.fr%s' % link.path)
コード例 #27
0
ファイル: focus_email.py プロジェクト: mehdinassih/bob-emploi
def blast_campaign(campaign_id,
                   action,
                   registered_from,
                   registered_to,
                   dry_run_email='',
                   user_hash=''):
    """Send a campaign of personalized emails."""
    if action == 'send' and auth.SECRET_SALT == auth.FAKE_SECRET_SALT:
        raise ValueError('Set the prod SECRET_SALT env var before continuing.')
    campaign = _CAMPAIGNS[campaign_id]
    template_id = campaign.mailjet_template
    selected_users = _DB.user.find(
        dict(
            campaign.mongo_filters, **{
                'profile.email': {
                    '$not': re.compile(r'@example.com$')
                },
                'registeredAt': {
                    '$gt': registered_from,
                    '$lt': registered_to,
                }
            }))
    email_count = 0
    email_errors = 0

    for user_dict in selected_users:
        user_id = user_dict.pop('_id')
        user = user_pb2.User()
        proto.parse_from_mongo(user_dict, user)
        user.user_id = str(user_id)

        if user_hash and not user.user_id.startswith(user_hash):
            continue

        if any(email.campaign_id == campaign_id for email in user.emails_sent):
            # We already sent the email to that person.
            continue

        template_vars = campaign.get_vars(user)
        if not template_vars:
            continue

        if action == 'list':
            logging.info('%s %s', user.user_id, user.profile.email)
            continue

        if action == 'dry-run':
            user.profile.email = dry_run_email
        if action in ('dry-run', 'send'):
            res = mail.send_template(template_id,
                                     user.profile,
                                     template_vars,
                                     sender_email=campaign.sender_email,
                                     sender_name=campaign.sender_email)
            logging.info('Email sent to %s', user.profile.email)

        if action == 'dry-run':
            try:
                res.raise_for_status()
            except requests.exceptions.HTTPError:
                raise ValueError(
                    'Could not send email for vars:\n{}'.format(template_vars))
        elif res.status_code != 200:
            logging.warning('Error while sending an email: %d',
                            res.status_code)
            email_errors += 1
            continue

        sent_response = res.json()
        message_id = next(iter(sent_response.get('Sent', [])),
                          {}).get('MessageID', 0)
        if not message_id:
            logging.warning('Impossible to retrieve the sent email ID:\n%s',
                            sent_response)
        if action == 'dry-run':
            return 1

        email_sent = user.emails_sent.add()
        email_sent.sent_at.GetCurrentTime()
        email_sent.sent_at.nanos = 0
        email_sent.mailjet_template = template_id
        email_sent.campaign_id = campaign_id
        email_sent.mailjet_message_id = message_id
        _DB.user.update_one({'_id': user_id}, {
            '$set': {
                'emailsSent':
                json_format.MessageToDict(user).get('emailsSent', []),
            }
        })
        email_count += 1
        if email_count % 100 == 0:
            print('{} emails sent ...'.format(email_count))

    if action == 'send':
        report.notify_slack(
            "Report for 3 month employment-status blast: I've sent {:d} emails (and got {:d} \
            errors).".format(email_count, email_errors))
    return email_count
コード例 #28
0
ファイル: auth.py プロジェクト: tkoutangni/bob-emploi
    def _password_authenticate(self, auth_request):
        now = int(time.time())
        response = user_pb2.AuthResponse()
        response.hash_salt = _timestamped_hash(now, auth_request.email)

        user_dict = self._db.user.find_one(
            {'profile.email': auth_request.email})

        if not user_dict:
            return self._password_register(auth_request, response)

        user_id = str(user_dict['_id'])
        user_auth_dict = self._db.user_auth.find_one({'_id': user_dict['_id']})

        if not user_auth_dict:
            if user_dict.get('googleId'):
                auth_method = ' (Google)'
            elif user_dict.get('facebookId'):
                auth_method = ' (Facebook)'
            else:
                auth_method = ''
            flask.abort(
                403,
                "L'utilisateur existe mais utilise un autre moyen de connexion%s."
                % auth_method)

        if not auth_request.hashed_password:
            # User exists but did not sent a passwordt: probably just getting some fresh salt.
            return response

        if auth_request.auth_token:
            self._reset_password(auth_request, user_id, user_auth_dict)
            user_dict['userId'] = user_id
            if not proto.parse_from_mongo(user_dict,
                                          response.authenticated_user):
                flask.abort(
                    500,
                    'Les données utilisateur sont corrompues dans la base de données.'
                )
            return response

        if not auth_request.hash_salt:
            # User exists but has not sent salt: probably just
            # getting some fresh salt.
            return response

        # Check that salt is valid.
        salt = auth_request.hash_salt
        try:
            if not _assert_valid_salt(salt, auth_request.email, now):
                return response
        except ValueError as error:
            flask.abort(
                403, "Le sel n'a pas été généré par ce serveur : %s." % error)

        stored_hashed_password = user_auth_dict.get('hashedPassword')

        hashed_password = hashlib.sha1()
        hashed_password.update(salt.encode('ascii'))
        hashed_password.update(stored_hashed_password.encode('ascii'))
        request_hashed_password = binascii.unhexlify(
            auth_request.hashed_password)
        if request_hashed_password != hashed_password.digest():
            flask.abort(403, 'Mot de passe erroné.')

        if not proto.parse_from_mongo(user_dict, response.authenticated_user):
            flask.abort(
                500,
                'Les données utilisateur sont corrompues dans la base de données.'
            )

        response.authenticated_user.user_id = user_id
        return response
コード例 #29
0
ファイル: proto_test.py プロジェクト: mehdinassih/bob-emploi
 def test_timestamp(self):
     """Parse correctly Python timestamps."""
     action = action_pb2.Action()
     now = datetime.datetime.now()
     self.assertTrue(proto.parse_from_mongo({'createdAt': now}, action))
     self.assertEqual(now, action.created_at.ToDatetime())