Exemplo n.º 1
0
 async def execute(self, m: Model):
     res = Reservation(**decrypt_json(self.app, m.booking_token, ttl=self.settings.ticket_ttl))
     assert self.session['user_id'] == res.user_id, "user ids don't match"
     async with self.conn.transaction():
         await self.conn.execute('DELETE FROM tickets WHERE reserve_action=$1', res.action_id)
         await self.conn.execute('SELECT check_tickets_remaining($1, $2)', res.event_id, self.settings.ticket_ttl)
         await record_action(self.request, self.session['user_id'], ActionTypes.cancel_reserved_tickets)
Exemplo n.º 2
0
 async def create_reservation():
     action_id = await db_conn.fetchval_b(
         'INSERT INTO actions (:values__names) VALUES :values RETURNING id',
         values=Values(
             company=factory.company_id,
             user_id=factory.user_id,
             type='reserve-tickets'
         )
     )
     await db_conn.execute_b(
         'INSERT INTO tickets (:values__names) VALUES :values',
         values=Values(
             event=factory.event_id,
             user_id=factory.user_id,
             reserve_action=action_id,
         )
     )
     return Reservation(
         user_id=factory.user_id,
         action_id=action_id,
         event_id=factory.event_id,
         price_cent=10_00,
         ticket_count=1,
         event_name='Foobar',
     )
Exemplo n.º 3
0
 async def book_free(self, reservation: Reservation, user_id=None):
     m = BookFreeModel(
         booking_token=encrypt_json(reservation.dict(),
                                    auth_fernet=self.app['auth_fernet']),
         book_action='book-free-tickets',
     )
     return await book_free(m, self.company_id,
                            {'user_id': user_id or self.user_id}, self.app,
                            self.conn)
Exemplo n.º 4
0
    async def create_reservation(self,
                                 user_id=None,
                                 *extra_user_ids,
                                 event_id=None,
                                 ticket_type_id=None):
        user_id = user_id or self.user_id
        action_id = await self.conn.fetchval_b(
            'INSERT INTO actions (:values__names) VALUES :values RETURNING id',
            values=Values(company=self.company_id,
                          user_id=user_id,
                          type=ActionTypes.reserve_tickets))
        ticket_type_id = ticket_type_id or self.ticket_type_id
        event_id = event_id or self.event_id

        assert event_id == await self.conn.fetchval(
            'SELECT event FROM ticket_types WHERE id=$1', ticket_type_id)
        price = await self.conn.fetchval(
            'SELECT price FROM ticket_types WHERE id=$1', ticket_type_id)
        ticket_values = [
            Values(
                event=event_id,
                user_id=user_id,
                reserve_action=action_id,
                ticket_type=ticket_type_id,
                price=price,
            )
        ]
        for extra_user_id in extra_user_ids:
            ticket_values.append(
                Values(
                    event=event_id,
                    user_id=extra_user_id,
                    reserve_action=action_id,
                    ticket_type=ticket_type_id,
                    price=price,
                ))
        await self.conn.execute_b(
            'INSERT INTO tickets (:values__names) VALUES :values',
            values=MultipleValues(*ticket_values))
        await self.conn.execute('SELECT check_tickets_remaining($1, $2)',
                                event_id, self.settings.ticket_ttl)
        return Reservation(
            user_id=user_id,
            action_id=action_id,
            event_id=event_id,
            price_cent=price and int(price * 100),
            ticket_count=1 + len(extra_user_ids),
            event_name=await
            self.conn.fetchval('SELECT name FROM events WHERE id=$1',
                               event_id),
        )
Exemplo n.º 5
0
 async def execute(self, m: Model):
     # no ttl since a user may try to cancel a reservation after it has expired
     res = Reservation(**decrypt_json(self.app, m.booking_token))
     async with self.conn.transaction():
         user_id = await self.conn.fetchval(
             'SELECT user_id FROM actions WHERE id=$1', res.action_id)
         v = await self.conn.execute(
             "DELETE FROM tickets WHERE reserve_action=$1 AND status='reserved'",
             res.action_id)
         if v == 'DELETE 0':
             # no tickets were deleted
             raise JsonErrors.HTTPBadRequest(message='no tickets deleted')
         await self.conn.execute('SELECT check_tickets_remaining($1, $2)',
                                 res.event_id, self.settings.ticket_ttl)
         await record_action(self.request,
                             user_id,
                             ActionTypes.cancel_reserved_tickets,
                             event_id=res.event_id)
Exemplo n.º 6
0
    async def execute(self, m: Model):  # noqa: C901 (ignore complexity)
        event_id = int(self.request.match_info['id'])
        ticket_count = len(m.tickets)

        if ticket_count > self.settings.max_tickets:
            raise JsonErrors.HTTPBadRequest(
                message='Too many tickets reserved')

        user_id = self.session['user_id']

        status, external_ticket_url, event_name, cover_costs_percentage = await self.conn.fetchrow(
            """
            SELECT e.status, e.external_ticket_url, e.name, c.cover_costs_percentage
            FROM events AS e
            JOIN categories c on e.category = c.id
            WHERE c.company=$1 AND e.id=$2
            """, self.request['company_id'], event_id)

        if status != 'published':
            raise JsonErrors.HTTPBadRequest(message='Event not published')

        if external_ticket_url is not None:
            raise JsonErrors.HTTPBadRequest(
                message='Cannot reserve ticket for an externally ticketed event'
            )

        r = await self.conn.fetchrow(
            'SELECT price FROM ticket_types WHERE event=$1 AND id=$2',
            event_id, m.ticket_type)
        if not r:
            raise JsonErrors.HTTPBadRequest(message='Ticket type not found')
        item_price, *_ = r

        if self.settings.ticket_reservation_precheck:  # should only be false during CheckViolationError tests
            tickets_remaining = await self.conn.fetchval(
                'SELECT check_tickets_remaining($1, $2)', event_id,
                self.settings.ticket_ttl)
            if tickets_remaining is not None and ticket_count > tickets_remaining:
                raise JsonErrors.HTTP470(
                    message=f'only {tickets_remaining} tickets remaining',
                    tickets_remaining=tickets_remaining)

        total_price, item_extra_donated = None, None
        if item_price:
            total_price = item_price * ticket_count
            if cover_costs_percentage and m.tickets[0].cover_costs:
                item_extra_donated = item_price * cover_costs_percentage / 100
                total_price += item_extra_donated * ticket_count

        try:
            async with self.conn.transaction():
                update_user_preferences = await self.create_users(m.tickets)

                action_id = await record_action_id(self.request,
                                                   user_id,
                                                   ActionTypes.reserve_tickets,
                                                   event_id=event_id)
                ticket_values = [
                    Values(
                        email=t.email and t.email.lower(),
                        first_name=t.first_name,
                        last_name=t.last_name,
                        extra_info=t.extra_info or None,
                    ) for t in m.tickets
                ]

                await self.conn.execute_b(
                    """
                    WITH v (email, first_name, last_name, extra_info) AS (VALUES :values)
                    INSERT INTO tickets (event, reserve_action, ticket_type, price, extra_donated, user_id,
                      first_name, last_name, extra_info)
                    SELECT :event, :reserve_action, :ticket_type, :price, :extra_donated, u.id,
                      v.first_name, v.last_name, v.extra_info FROM v
                    LEFT JOIN users AS u ON v.email=u.email AND u.company=:company_id
                    """,
                    event=event_id,
                    reserve_action=action_id,
                    ticket_type=m.ticket_type,
                    price=item_price,
                    extra_donated=item_extra_donated,
                    company_id=self.request['company_id'],
                    values=MultipleValues(*ticket_values),
                )
                await self.conn.execute(
                    'SELECT check_tickets_remaining($1, $2)', event_id,
                    self.settings.ticket_ttl)
        except CheckViolationError as exc:
            if exc.constraint_name != 'ticket_limit_check':  # pragma: no branch
                raise  # pragma: no cover
            logger.warning('CheckViolationError: %s', exc)
            raise JsonErrors.HTTPBadRequest(
                message='insufficient tickets remaining')

        res = Reservation(
            user_id=user_id,
            action_id=action_id,
            price_cent=total_price and int(total_price * 100),
            event_id=event_id,
            ticket_count=ticket_count,
            event_name=event_name,
        )
        if total_price:
            client_secret = await stripe_buy_intent(res,
                                                    self.request['company_id'],
                                                    self.app, self.conn)
        else:
            client_secret = None
        if update_user_preferences:
            # has to happen after the transactions is finished
            await self.app['donorfy_actor'].update_user(
                self.request['session']['user_id'], update_user=False)
        return {
            'booking_token':
            encrypt_json(self.app, res.dict()),
            'action_id':
            action_id,
            'ticket_count':
            ticket_count,
            'item_price':
            item_price and float(item_price),
            'extra_donated':
            item_extra_donated and float(item_extra_donated * ticket_count),
            'total_price':
            total_price and float(total_price),
            'timeout':
            int(time()) + self.settings.ticket_ttl - 30,
            'client_secret':
            client_secret,
        }
Exemplo n.º 7
0
    async def execute(self, m: Model):
        event_id = int(self.request.match_info['id'])
        ticket_count = len(m.tickets)
        if ticket_count < 1:
            raise JsonErrors.HTTPBadRequest(message='at least one ticket must be purchased')

        status, event_price, event_name = await self.conn.fetchrow(
            """
            SELECT e.status, e.price, e.name
            FROM events AS e
            JOIN categories c on e.category = c.id
            WHERE c.company=$1 AND e.id=$2
            """,
            self.request['company_id'], event_id
        )
        if status != 'published':
            raise JsonErrors.HTTPBadRequest(message='Event not published')

        tickets_remaining = await self.conn.fetchval(
            'SELECT check_tickets_remaining($1, $2)',
            event_id, self.settings.ticket_ttl
        )

        if tickets_remaining is not None and ticket_count > tickets_remaining:
            raise JsonErrors.HTTP470(message=f'only {tickets_remaining} tickets remaining',
                                     tickets_remaining=tickets_remaining)

        # TODO check user isn't already booked

        try:
            async with self.conn.transaction():
                user_lookup = await self.create_users(m.tickets)

                action_id = await record_action_id(self.request, self.session['user_id'], ActionTypes.reserve_tickets)
                await self.conn.execute_b(
                    'INSERT INTO tickets (:values__names) VALUES :values',
                    values=MultipleValues(*[
                        Values(
                            event=event_id,
                            user_id=user_lookup[t.email.lower()] if t.email else None,
                            reserve_action=action_id,
                            extra=to_json_if(t.dict(include={'dietary_req', 'extra_info'})),
                        )
                        for t in m.tickets
                    ])
                )
                await self.conn.execute('SELECT check_tickets_remaining($1, $2)', event_id, self.settings.ticket_ttl)
        except CheckViolationError as e:
            logger.warning('CheckViolationError: %s', e)
            raise JsonErrors.HTTPBadRequest(message='insufficient tickets remaining')

        user = await self.conn.fetchrow(
            """
            SELECT id, full_name(first_name, last_name, email) AS name, email, role
            FROM users
            WHERE id=$1
            """,
            self.session['user_id']
        )
        # TODO needs to work when the event is free
        price_cent = int(event_price * ticket_count * 100)
        res = Reservation(
            user_id=self.session['user_id'],
            action_id=action_id,
            price_cent=price_cent,
            event_id=event_id,
            ticket_count=ticket_count,
            event_name=event_name,
        )
        return {
            'booking_token': encrypt_json(self.app, res.dict()),
            'ticket_count': ticket_count,
            'item_price_cent': int(event_price * 100),
            'total_price_cent': price_cent,
            'user': dict(user),
            'timeout': int(time()) + self.settings.ticket_ttl,
        }