Ejemplo n.º 1
0
class UserIssuesView(BaseView):
    """
    Gets or adds to the users' list of issues.
    """
    url = f"/users/{{id:{USER_IDENTIFIER_REGEX}}}/issues"
    name = "user_issues"
    with_issues = match_getter(get_issues, "issues", user='******')
    with_user = match_getter(get_user, 'user', user_id='id')

    @with_user
    @with_issues
    @docs(summary="Get All Issues For User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @returns(JSendSchema.of(issues=Many(IssueSchema())))
    async def get(self, user, issues):
        return {
            "status": JSendStatus.SUCCESS,
            "data": {
                "issues":
                [issue.serialize(self.request.app.router) for issue in issues]
            }
        }

    @with_user
    @docs(summary="Open Issue For User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @expects(IssueSchema(only=('description', 'bike_identifier')))
    @returns(
        JSendSchema.of(issue=IssueSchema(only=('id', 'user_id', 'user_url',
                                               'bike_identifier',
                                               'description', 'opened_at'))))
    async def post(self, user):
        issue_data = {
            "description": self.request["data"]["description"],
            "user": user
        }

        if "bike_identifier" in self.request["data"]:
            issue_data["bike"] = await get_bike(
                identifier=self.request["data"]["bike_identifier"])

        issue = await open_issue(**issue_data)

        return {
            "status": JSendStatus.SUCCESS,
            "data": {
                "issue": issue.serialize(self.request.app.router)
            }
        }
Ejemplo n.º 2
0
class UserCurrentReservationView(BaseView):
    """
    Gets the users' current reservation.
    """
    url = f"/users/{{id:{USER_IDENTIFIER_REGEX}}}/reservations/current"

    with_reservation = match_getter(current_reservations,
                                    "reservations",
                                    user="******")
    with_user = match_getter(get_user, "user", user_id="id")

    @with_user
    @with_reservation
    @docs(summary="Get Current Reservations For User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @returns(JSendSchema.of(reservations=Many(CurrentReservationSchema())))
    async def get(self, user, reservations: List[Reservation]):
        return {
            "status": JSendStatus.SUCCESS,
            "data": {
                "reservations": [
                    reservation.serialize(self.request.app.router,
                                          self.reservation_manager)
                    for reservation in reservations
                ]
            }
        }
Ejemplo n.º 3
0
class UserRentalsView(BaseView):
    """
    Gets or adds to the users list of rentals.
    """
    url = f"/users/{{id:{USER_IDENTIFIER_REGEX}}}/rentals"
    name = "user_rentals"
    with_user = match_getter(get_user, 'user', user_id='id')
    with_rentals = match_getter(get_rentals, 'rentals', user='******')

    @with_user
    @with_rentals
    @docs(summary="Get All Rentals For User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @returns(JSendSchema.of(rentals=Many(RentalSchema())))
    async def get(self, user, rentals: List[Rental]):
        return {
            "status": JSendStatus.SUCCESS,
            "data": {
                "rentals": [
                    await rental.serialize(self.rental_manager,
                                           self.request.app.router)
                    for rental in rentals
                ]
            }
        }
Ejemplo n.º 4
0
class UserView(BaseView):
    """
    Gets, replaces or deletes a single user.
    """
    url = f"/users/{{id:{USER_IDENTIFIER_REGEX}}}"
    name = "user"
    with_user = match_getter(get_user, 'user', user_id='id')

    @with_user
    @docs(summary="Get A User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @expects(None)
    @returns(JSendSchema.of(user=UserSchema()))
    async def get(self, user: User):
        return {
            "status": JSendStatus.SUCCESS,
            "data": {
                "user": user.serialize()
            }
        }

    @with_user
    @docs(summary="Replace A User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @expects(UserSchema(only=('first', 'email')))
    @returns(JSendSchema.of(user=UserSchema()))
    async def put(self, user: User):
        user = await update_user(user, **self.request["data"])
        return {"status": JSendStatus.SUCCESS, "data": {"user": user}}

    @with_user
    @docs(summary="Delete A User")
    @requires(UserMatchesToken() | UserIsAdmin())
    async def delete(self, user: User):
        if user.can_pay:
            await self.payment_manager.delete_customer(user)
        await delete_user(user)
        raise web.HTTPNoContent
Ejemplo n.º 5
0
class UserCurrentRentalView(BaseView):
    """
    Gets or ends the user's current rental.
    """
    url = f"/users/{{id:{USER_IDENTIFIER_REGEX}}}/rentals/current"
    name = "user_current_rental"
    with_user = match_getter(get_user, 'user', user_id='id')

    @with_user
    @docs(summary="Get Current Rental For User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @returns(no_rental=(JSendSchema(), web.HTTPNotFound),
             rental_exists=JSendSchema.of(rental=CurrentRentalSchema(
                 only=('id', 'bike_identifier', 'bike_url', 'user_id',
                       'user_url', 'start_time', 'is_active',
                       'estimated_price', 'start_location',
                       'current_location'))))
    async def get(self, user: User):
        if not self.rental_manager.has_active_rental(user):
            return "no_rental", {
                "status": JSendStatus.FAIL,
                "data": {
                    "message": f"You have no current rental."
                }
            }

        current_rental, start_location, current_location = await self.rental_manager.active_rental(
            user, with_locations=True)
        return "rental_exists", {
            "status": JSendStatus.SUCCESS,
            "data": {
                "rental":
                await
                current_rental.serialize(self.rental_manager,
                                         self.bike_connection_manager,
                                         self.reservation_manager,
                                         self.request.app.router,
                                         start_location=start_location,
                                         current_location=current_location)
            }
        }
Ejemplo n.º 6
0
class UserEndCurrentRentalView(BaseView):
    """"""

    url = f"/users/{{id:{USER_IDENTIFIER_REGEX}}}/rentals/current/{{action}}"
    name = "user_end_current_rental"
    with_user = match_getter(get_user, 'user', user_id='id')
    actions = ("cancel", "complete")

    @with_user
    @docs(summary="End Rental For User")
    @requires(UserMatchesToken() | UserIsAdmin())
    @returns(
        no_rental=(JSendSchema(), web.HTTPNotFound),
        invalid_action=(JSendSchema(), web.HTTPNotFound),
        rental_completed=JSendSchema.of(rental=RentalSchema(),
                                        action=String(),
                                        receipt_url=Url(allow_none=True)),
    )
    async def patch(self, user: User):
        """
        Ends a rental for a user, in one of two ways:

        - ``PATCH /users/me/rentals/current/cancel`` cancels the rental
        - ``PATCH /users/me/rentals/current/complete`` completes the rental
        """

        if not self.rental_manager.has_active_rental(user):
            return "no_rental", {
                "status": JSendStatus.FAIL,
                "data": {
                    "message": "You have no current rental."
                }
            }

        end_type = self.request.match_info["action"]
        if end_type not in self.actions:
            return "invalid_action", {
                "status": JSendStatus.FAIL,
                "data": {
                    "message":
                    f"Invalid action. Pick between {', '.join(self.actions)}",
                    "actions": self.actions
                }
            }

        if end_type == "complete":
            rental, receipt_url = await self.rental_manager.finish(user)
        elif end_type == "cancel":
            rental = await self.rental_manager.cancel(user)
            receipt_url = None
        else:
            raise Exception

        return "rental_completed", {
            "status": JSendStatus.SUCCESS,
            "data": {
                "rental":
                await rental.serialize(self.rental_manager,
                                       self.bike_connection_manager,
                                       self.reservation_manager,
                                       self.request.app.router),
                "action":
                "canceled" if end_type == "cancel" else "completed",
                "receipt_url":
                receipt_url,
            }
        }
Ejemplo n.º 7
0
class BikeRentalsView(BaseView):
    """
    Gets the rentals for a single bike.
    """
    url = f"/bikes/{{identifier:{BIKE_IDENTIFIER_REGEX}}}/rentals"
    with_bike = match_getter(get_bike, 'bike', identifier=('identifier', str))
    with_user = match_getter(get_user, 'user', firebase_id=GetFrom.AUTH_HEADER)

    @with_bike
    @with_user
    @docs(summary="Get Past Rentals For Bike")
    @requires(UserIsAdmin())
    @returns(JSendSchema.of(rentals=Many(RentalSchema())))
    async def get(self, bike: Bike, user):
        return {
            "status": JSendStatus.SUCCESS,
            "data": {
                "rentals": [
                    await rental.serialize(self.rental_manager,
                                           self.bike_connection_manager,
                                           self.reservation_manager,
                                           self.request.app.router)
                    for rental in await get_rentals_for_bike(bike=bike)
                ]
            }
        }

    @with_bike
    @with_user
    @docs(summary="Start A New Rental")
    @requires(UserMatchesToken() & UserCanPay() & BikeNotInUse()
              & BikeNotBroken(max_issues=1))
    @returns(rental_created=JSendSchema.of(rental=CurrentRentalSchema(
        exclude=('user', 'bike', 'events'))),
             active_rental=JSendSchema(),
             reservation_error=JSendSchema())
    async def post(self, bike: Bike, user: User):
        """
        It is most logical that a rental is created on a bike resource.
        This metaphor matches real life the best, as it resembles picking bike off the rack.
        A user may start a rental on any bike that is not currently in use, by simply
        sending a POST request to the bike's rentals resource ``/api/v1/bikes/rentals``
        with the user's firebase token.
        """
        reservations = await current_reservations(user)

        if reservations:
            try:
                rental, start_location = await self.reservation_manager.claim(
                    user, bike)
            except CollectionError:
                # they can try and rent it normally
                reservations = []
            except ReservationError as e:
                return "reservation_error", {
                    "status": JSendStatus.FAIL,
                    "data": {
                        "message": str(e)
                    }
                }
            except ActiveRentalError as e:
                return "active_rental", {
                    "status": JSendStatus.FAIL,
                    "data": {
                        "message":
                        "You already have an active rental!",
                        "rental_id":
                        e.rental_id,
                        "url":
                        str(self.request.app.router["me"].url_for(
                            tail="/rentals/current"))
                    }
                }

        if not reservations:
            try:
                rental, start_location = await self.rental_manager.create(
                    user, bike)
            except ActiveRentalError as e:
                return "active_rental", {
                    "status": JSendStatus.FAIL,
                    "data": {
                        "message":
                        "You already have an active rental!",
                        "rental_id":
                        e.rental_id,
                        "url":
                        str(self.request.app.router["me"].url_for(
                            tail="/rentals/current"))
                    }
                }

        return "rental_created", {
            "status": JSendStatus.SUCCESS,
            "data": {
                "rental":
                await rental.serialize(self.rental_manager,
                                       self.bike_connection_manager,
                                       self.reservation_manager,
                                       self.request.app.router,
                                       start_location=start_location,
                                       current_location=start_location)
            }
        }