def _assert_valid_duration(self, start_time, end_time): """ Makes sure all durations are valid. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :raises: DAOException :rtype: NoneType :returns: None """ if start_time >= end_time: raise DAOException( 'Invalid start time. Start must be before the end.') if end_time.day != start_time.day: raise DAOException( 'A schedule must start and end in the same day.') min_diff = math.ceil((end_time - start_time).total_seconds() / 60) if min_diff not in [5, 15, 30, 60]: if min_diff > 60 and min_diff % 60 == 0: return raise DAOException( 'Invalid duration. Attempted duration is not of valid length.')
def put(self, user_id, **args): _update = {} current_user = None plaintext = args.get('plaintext_password') current_pass = args.get('current_password') if plaintext and plaintext != '': current_user = self.get(user_id) if current_user is None: logging.info( 'Attempted to update plaintext password for non-existent' 'account {0}'.format(user_id)) raise DAOException('Requested user to update does not exist.') for k, v in args.items(): if k == 'plaintext_password' and (v != '' or v is not None): if UserTable._bcrypt_compare(current_pass, current_user.password): _update['password'] = UserTable._bcrypt_password(plaintext) continue else: raise DAOException('Invalid current password.') if k == 'current_password': continue _update[k] = v if len(_update.keys()) != 0: User.query.filter_by(public_id=str(user_id)).update(_update) db.session.commit() return self.get(user_id)
def post(self, start_time, end_time, user_id): """ Handles the creation of a new schedule. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :param str user_id: The user to create the schedule for. :rtype: ScheduleTable :return: The newly scheduled table. """ self._assert_valid_duration(start_time, end_time) self._assert_no_duration_overlap(start_time, end_time, user_id) self._assert_not_in_past(start_time, end_time) user_info = db.session.query(User).filter( User.public_id == user_id).first() if user_info is None: raise DAOException('Invalid user requested. Try again.') new_schedule = Schedule(start_time, end_time, user_id, user_info.local_tz) exec_and_commit(db.session.add, new_schedule) return new_schedule
def _assert_schedule_exists(self, start_time, end_time, scheduled_user_id): """ Asserts that the scheduled user has a schedule open for the event. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :param str scheduled_user_id: The user ID to retrieve schedules for. :raises: DAOException :rtype: NoneType :returns: None """ if end_time.tzinfo is not None: end_time = end_time.replace(tzinfo=None) if start_time.tzinfo is not None: start_time = start_time.replace(tzinfo=None) schedule = db.session.query(Schedule).filter( Schedule.user_id == scheduled_user_id, func.lower(Schedule.utc_duration) <= start_time, func.upper(Schedule.utc_duration) >= end_time, ) schedule = schedule.first() if schedule is None: raise DAOException( 'Invalid event. User does not have an open schedule time slot ' 'for the requested booking.')
def _assert_no_duration_overlap(self, start_time, end_time, user_id): """ Retrieves all of a users schedules and asserts that there is no schedule overlap. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :param str user_id: The user ID to filter by. :raises: DAOException :rtype: NoneType :returns: Nothing """ schedules = db.session.query(Schedule).filter( func.lower(Schedule.utc_duration) <= start_time, func.upper(Schedule.utc_duration) >= end_time, Schedule.user_id == user_id, Schedule.day_number == end_time.day, ).all() if not schedules: return raise DAOException( "Invalid schedule. This overlaps with a previous schedule.")
def test_rollback_on_failure(self, transaction_mock): with app.app_context(): with self.assertRaises(FacadeException) as e: new_schedule = Schedule( datetime.utcnow().replace(hour=1) + timedelta(days=1), datetime.utcnow().replace(hour=5) + timedelta(days=1), self.scheduled_user, 'UTC', ) db.session.add(new_schedule) db.session.commit() transaction_mock.side_effect = DAOException('test') new_event = EventFacade().create_new_event( self.scheduling_user, self.scheduled_user, (datetime.utcnow().replace(hour=2) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'), (datetime.utcnow().replace(hour=3) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'), 'UTC', 'test3-1234ko1234', 'fake-nonce', ) self.assertEqual(e.msg, 'test') found_event = db.session.query(Event).filter_by( notes='test3-1234ko1234', ).first() self.assertIsNone(found_event)
def login(self, user_challenge, plaintext_password): """ Handles logging in a user. :param str user_challenge: The email/username to get the user by :param str plaintext_password: The password to compare against. :raises: BadPasswordException :rtype: UserTable :return: The logged in user. """ user_dao = UserDAO() user_info = user_dao.get_by_email_or_username(user_challenge) if user_info is None: raise DAOException("Invalid email. User does not exist.") if user_info.compare_password(plaintext_password): return user_info raise DAOException("Invalid credentials. Try again.")
def lookup_enum_type(enum_type): """ Returns a time period value based :param str enum_type: A time period type like "month", "week". :rtype: TimePeriodEnum :return: The enum value corresponding to the input. """ try: return TimePeriodEnum.__value_lookup[enum_type.lower()] except KeyError: raise DAOException("Invalid time period type.")
def put(self, schedule_id, start_time, end_time, user_id): """ Updates a schedule to new duration. :param int schedule_id: The schedule to update. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :param str user_id: The user to create the schedule for. :rtype: ScheduleTable :return: The newly scheduled table. """ self._assert_valid_duration(start_time, end_time) schedule = self.get_by_schedule_id(schedule_id) if schedule is None: raise DAOException('Requested schedule is invalid. Try again.') if start_time < schedule.utc_open.replace( tzinfo=None) or end_time > schedule.utc_end.replace( tzinfo=None): self._assert_no_duration_overlap(start_time, end_time, user_id) rows_affected = Schedule.query.filter( Schedule.user_id == user_id, Schedule.public_id == schedule_id).update({ 'utc_duration': DateTimeRange(start_time, end_time), }) db.session.commit() if rows_affected == 0: raise DAOException( 'Failed to update schedule. Schedule not found.') return self.get_by_schedule_id(schedule_id)
def regenerate_token(self, email, token_choice): """ Handles regenerating a token for a user. :param str email: The user's email to regenerate for. :param str token_choice: `verify_token` or `reset_token`. :rtype: str :returns: The newly generated token. """ found_user = self.get_by_email(email) if found_user is None: logging.error('Failed to regenerate token for email {0}. ' 'Email not exists.'.format(email)) raise DAOException('Failed to find requested user account.', HTTPStatus.NOT_FOUND) if found_user.is_validated and token_choice == 'verify_token': logging.error('Requested user {0} is already activated. Skipping.') raise DAOException('Requested account is already activated.') return self._regen_token(email, token_choice), found_user
def cancel_subscription(self, user_id): """ Handles cancelling a subscription for the user. :param str user_id: The customer whom we want to cancel the subscription for. :rtype: SubscriptionTable :return: The recently cancelled subscription """ found_subscription = self.get_subscription_by_user_id(user_id) if found_subscription is None: raise DAOException( 'Subscription not found.', status_code=HTTPStatus.NOT_FOUND ) db.session.query( Subscription ).filter_by( public_id=found_subscription.public_id, ).update({ 'is_deleted': True, 'date_ended': datetime.utcnow(), }) try: db.session.commit() except sqlalchemy.exc.IntegrityError as e: logging.error( 'Failed to remove subscription for user {0} with ' 'exc: {1}'.format( user_id, e, ) ) raise DAOException('Failed to cancel subscription.') return found_subscription
def create_customer(self, bt_customer_id, cc_token, first_name, last_name, user_id, *, is_default=False, skip_commit=False): """ Handles creating a new mirrored customer in our database which represents the necessary information in our system. Note: these values also persist in Braintree. :param str bt_customer_id: The unique identifier generated by Braintree. :param str cc_token: A uniquely generated (by us) value for the payment method. :param str first_name: The customer's first name. :param str last_name: The customer's last name. :param str user_id: The customer's UUID. :param bool is_default: If we want this new payment method to be the default payment method. :rtype: CustomerTable :return: The newly created customer. """ new_customer = Customer(bt_customer_id, cc_token, first_name, last_name, user_id, is_default=is_default) try: exec_and_commit( db.session.add, new_customer, skip_commit=skip_commit, ) except Exception as e: logging.critical( 'Failed to create a new customer in our database: {0} with' 'exception of: {1}'.format( new_customer, e, )) raise DAOException('Failed to create new customer.') return new_customer
def get_master_merchant(self): """ Handles retrieving the master merchant account. :rtype: MasterMerchantTable :return: The master merchant info. """ retval = db.session.query(MasterMerchant).filter_by( environment=current_app.config.get('ENVIRONMENT', 'dev').upper()).first() if retval is None: raise DAOException('Requested merchant account does not exist.') return retval
def _assert_valid_duration(self, start_time, end_time): """ Makes sure all durations are valid. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :raises: DAOException :rtype: NoneType :returns: Nothing """ if start_time >= end_time: raise DAOException( "Invalid start time. Start must be before the end.")
def get_submerchant_by_id(self, submerchant_public_id): """ Retrieves the submerchant from the DB by it's public ID. :param str submerchant_public_id: The submerchan'ts public UUID :rtype: SubmerchantTable :return: The submerchant. """ try: return db.session.query(SubMerchant).filter_by( user_id=submerchant_public_id, ).first() except Exception as e: logging.error('Failed to query submerchant w/ public ID {0} ' 'w/ exc of {1}'.format(submerchant_public_id, e)) raise DAOException('No submerchant found with that id.')
def _assert_not_in_past(self, start_time, end_time): """ Asserts the new schedule is not in the past. :param datetime.datetime start_time: The date at which the schedule opens. :param datetime.datetime end_time: The date at which the schedule closes. :raises: DAOException :rtype: NoneType :returns: Nothing """ current_time = datetime.now(start_time.tzinfo) if start_time < current_time: raise DAOException('Cannot create schedules in the past.')
def get_customer_by_user_id(self, user_id): """ Handles retrieving a customer for the user. :param str user_id: The customer's UUID. :rtype: CustomerTable :return: The requested customer. """ found_customer = db.session.query(Customer, ).filter( Customer.user_id == user_id, ).first() if found_customer is None: logging.error('Failed to retrieve customer by User ID {0}'.format( user_id, )) raise DAOException('Failed to retrieve selected customer.') return found_customer
def get_customer_by_public_id(self, customer_public_id): """ Handles retrieving a customer by it's public ID. :param str customer_public_id: The public ID for the customer :rtype: CustomerTable :return: The requested customer. """ found_customer = db.session.query(Customer).filter_by( public_id=customer_public_id).first() if found_customer is None: logging.error('Failed to retrieve customer by ID {0}'.format( customer_public_id, )) raise DAOException('Failed to retrieve selected customer.') return found_customer
def eradicate_event(self, event_public_id): """ Handles event rollbacks in case the payment fails. :param str event_public_id: The event we're wanting to delete :rtype: EventTable :return: The event which was eradicated. """ found_event = db.session.query(Event).filter_by( public_id=event_public_id).first() if found_event is None: raise DAOException('Failed to delete event. Event does not exist.') exec_and_commit(db.session.delete, found_event) return found_event
def delete(self, user_id, schedule_id): """ Deletes a schedule by its id. :param str user_id: The user to delete the schedule for. :param int schedule_id: The schedule to delete. """ rows_affected = Schedule.query.filter( Schedule.user_id == user_id, Schedule.public_id == schedule_id, ).delete() db.session.commit() if rows_affected == 0: raise DAOException( 'Failed to delete schedule. Schedule not found.')
def get_default_for_user(self, user_public_id): """ Retrieves the default address for a user. :param str user_public_id: The user's public ID. :rtype: Address :return: The default address associated with a user. """ address = db.session.query(Address).filter_by( is_deleted=False, is_default=True, ).join(User).filter_by(public_id=user_public_id).first() if address is None: logging.error('Failed to find default address for user {0}'.format( user_public_id)) raise DAOException('Failed to find default address.', HTTPStatus.NOT_FOUND) return address
def get_by_email(self, email): """ Retrieves user info by the user's email. :param str email: The user to retrieve by email. Emails are unique so this is a safe retrieval. :rtype: UserTable :return: The UserTable row or None """ found_user = db.session.query(User).filter_by(email=email).first() if found_user is None: logging.error( 'Failed to find requested user by email {0}.'.format(email)) raise DAOException( 'Failed to find requested user.', HTTPStatus.NOT_FOUND, ) return found_user
def create_subscription(self, bt_sub_id, user_id, *, plan_id='Basic0x01', skip_commit=False): """ Handles creating a subscription record in the db. :param str bt_sub_id: The unique identifier generated by braintree when subscriptions are created. :param str user_id: The customer's UUID. :param str plan_id: The plan we want to subscribe to. :param bool skip_commit: Handles skipping the commit to create a pseudo transaction. :rtype: SubscriptionTable :return: The newly created subscription """ new_subscription = Subscription( self.customer_dao.get_customer_by_user_id(user_id).public_id, user_id, bt_sub_id, plan_id=plan_id, ) try: exec_and_commit( db.session.add, new_subscription, skip_commit=skip_commit, ) except Exception as e: logging.critical( 'Failed to create a new subscription in our database: {0} with' 'exception of: {1}'.format( new_subscription, e, ) ) raise DAOException( 'Failed to create new subscription.' ) return new_subscription
def get(self, user_id): """ Retrieves a single user by its user id. :param str user_id: The User ID to look up. :rtype: UserTable :return: The UserTable row or None. """ found_user = User.query.filter( User.public_id == str(user_id), User.is_deleted == False).limit(1).first() if found_user is None: logging.error( 'Failed to find requested user by id {0}.'.format(user_id)) raise DAOException( 'Failed to find requested user.', HTTPStatus.NOT_FOUND, ) return found_user
def get_by_event_id(self, user_id, event_id): """ Returns the information for an event. :param str user_id: Either the scheduled or scheduling user. :param str event_id: The event to retrieve :rtype: EventTable :return: The requested event. """ scheduled = aliased(User) scheduling = aliased(User) event = db.session.query( Event, scheduled, scheduling, ).filter( or_( Event.scheduled_user_id == user_id, Event.scheduling_user_id == user_id, ), Event.public_id == event_id, ).join( scheduled, Event.scheduled_user_id == scheduled.public_id, ).join( scheduling, Event.scheduling_user_id == scheduling.public_id, ).first() if event is None: logging.error( 'Failed to retrieve event ({0}) for user {1}.'.format( event_id, user_id, )) raise DAOException( 'Requested event not found. Please refresh and try again.') return event
def get_by_public_id(self, public_id, user_public_id): """ Retrieves a single address by its public ID. :param str public_id: The UUID associated with an address. :param str user_public_id: The user's public ID. :rtype: AddressTable :return: A single address object. """ address = db.session.query(Address).filter_by( public_id=public_id, is_deleted=False, ).join(User).filter_by(public_id=user_public_id).first() if address is None: logging.error( 'Failed to find requested address {0} for user {1}'.format( public_id, user_public_id)) raise DAOException('Failed to find requested address.', HTTPStatus.NOT_FOUND) return address
def get_by_email_or_username(self, user_challenge): """ Handles retrieving a user by either username or email. :param str user_challenge: Email or username. :rtype: UserTable :return: The found user/email. """ found_user = db.session.query(User).filter( or_(User.email == user_challenge, User.username == user_challenge)).first() if found_user is None: logging.error( 'Failed to find requested user by email/username {0}.'.format( user_challenge, )) raise DAOException( 'Failed to find requested user.', HTTPStatus.NOT_FOUND, ) return found_user
def get_subscription_by_public_id(self, subscription_public_id): """ Handles retrieving a subscription by it's public ID. :param str subscription_public_id: The public ID for the customer :rtype: SubscriptionTable :return: The requested subscription. """ found_subscription = db.session.query( Subscription ).filter_by( public_id=subscription_public_id ).first() if found_subscription is None: logging.error( 'Failed to retrieve subscription by ID {0}'.format( subscription_public_id, ) ) raise DAOException('Failed to retrieve selected subscription.') return found_subscription
def get_subscription_by_user_id(self, user_id): """ Handles retrieving a subscription for the user. :param str user_id: The customer's UUID. :rtype: SubscriptionTable :return: The requested subscription. """ found_subscription = db.session.query( Subscription, ).filter( Subscription.user_id == user_id, ).first() if found_subscription is None: logging.error( 'Failed to retrieve subscription by User ID {0}'.format( user_id, ) ) raise DAOException('Failed to retrieve subscription info.') return found_subscription
def delete_customer(self, customer_public_id): """ Handles setting a customer account as deleted. (Soft delete) :param str customer_public_id: The UUID identifying the customer account in the database. :rtype: CustomerTable :return: The recently deleted customer. """ found_customer = self.get_customer_by_public_id(customer_public_id) db.session.query(Customer).filter_by( public_id=customer_public_id, ).update({"is_deleted": True}) try: db.session.commit() except sqlalchemy.exc.IntegrityError as e: logging.error('Failed to remove customer account {0}'.format( customer_public_id, )) raise DAOException('Failed to remove selected customer.') return found_customer