async def login(request): m = await parse_request(request, LoginModel, headers_=HEADER_CROSS_ORIGIN) repeat_cache_key = f'login-attempt:{get_ip(request)}' login_attempted = await request.app['redis'].get(repeat_cache_key) if login_attempted: await check_grecaptcha(m, request, error_headers=HEADER_CROSS_ORIGIN) if m.password != request.app['settings'].dummy_password: r = await request['conn'].fetchrow(LOGIN_USER_SQL, request['company_id'], m.email) if r: user = dict(r) password_hash = user.pop('password_hash') else: # still try hashing to avoid timing attack user = dict() password_hash = None password_hash = password_hash or request.app['dummy_password_hash'] if bcrypt.checkpw(m.password.encode(), password_hash.encode()): return successful_login(user, request.app, HEADER_CROSS_ORIGIN) await request.app['redis'].setex(repeat_cache_key, 60, b'1') return json_response(status='invalid', message='invalid email or password', headers_=HEADER_CROSS_ORIGIN, status_=470)
async def company_set_footer_link(request): m = await parse_request(request, FooterLinksModel) v = json.dumps([l.dict() for l in m.links], separators=(',', ':')) await request['conn'].execute( 'UPDATE companies SET footer_links=$1 WHERE id=$2', v, request['company_id']) return json_response(status='success')
async def email_def_retrieve(request): trigger = get_trigger(request) email_def = await request['conn'].fetchrow( 'SELECT active, subject, title, body FROM email_definitions WHERE trigger=$1 and company=$2', trigger, request['company_id'], ) if email_def: data = { 'trigger': trigger, 'customised': True, 'active': email_def['active'], 'subject': email_def['subject'], 'title': email_def['title'], 'body': email_def['body'], } else: defaults = EMAIL_DEFAULTS[Triggers(trigger)] data = { 'trigger': trigger, 'customised': False, 'active': True, 'subject': defaults['subject'], 'title': defaults['title'], 'body': defaults['body'].strip('\n '), } return json_response(**data)
async def donation_image_upload(request): co_id = request['company_id'] don_opt_id = int(request.match_info['pk']) r = await request['conn'].fetchrow( """ SELECT co.slug, cat.slug, d.image FROM donation_options AS d JOIN categories AS cat ON d.category = cat.id JOIN companies AS co ON cat.company = co.id WHERE d.id = $1 AND cat.company = $2 """, don_opt_id, co_id, ) if not r: raise JsonErrors.HTTPNotFound(message='donation option not found') co_slug, cat_slug, old_image = r content = await request_image(request, expected_size=IMAGE_SIZE) upload_path = Path(co_slug) / cat_slug / str(don_opt_id) image_url = await upload_other( content, upload_path=upload_path, settings=request.app['settings'], req_size=IMAGE_SIZE, thumb=True, ) await request['conn'].execute('UPDATE donation_options SET image=$1 WHERE id=$2', image_url, don_opt_id) if old_image: await delete_image(old_image, request.app['settings']) return json_response(status='success')
async def booking_info(request): event_id = await check_event_sig(request) conn: BuildPgConnection = request['conn'] settings = request.app['settings'] tickets_remaining = await conn.fetchval( 'SELECT check_tickets_remaining($1, $2)', event_id, settings.ticket_ttl) existing_tickets = await conn.fetchval( """ SELECT COUNT(*) FROM tickets JOIN actions AS a ON tickets.reserve_action = a.id WHERE tickets.event=$1 AND a.user_id=$2 AND status='booked' """, event_id, request['session']['user_id'], ) ticket_types = await conn.fetch( "SELECT id, name, price::float FROM ticket_types WHERE event=$1 AND mode='ticket' AND active=TRUE ORDER BY id", event_id, ) return json_response( tickets_remaining=tickets_remaining if (tickets_remaining and tickets_remaining < 10) else None, existing_tickets=existing_tickets or 0, ticket_types=[dict(tt) for tt in ticket_types], )
async def event_tickets(request): event_id = await _check_event_permissions(request) not_admin = request['session']['role'] != 'admin' tickets = [] settings = request.app['settings'] conn: BuildPgConnection = request['conn'] for t in await conn.fetch(event_tickets_sql, event_id): ticket = { 'ticket_id': ticket_id_signed(t['id'], settings), **t, } if not_admin: ticket.pop('guest_email') ticket.pop('buyer_email') tickets.append(ticket) waiting_list = [ dict(r) for r in await conn.fetch(event_waiting_list_sql, event_id) ] if not_admin: [r.pop('email') for r in waiting_list] donations = [ dict(r) for r in await conn.fetch(event_donations_sql, event_id) ] if not_admin: [r.pop('user_email') for r in donations] return json_response(tickets=tickets, waiting_list=waiting_list, donations=donations)
async def company_upload(request): field_name = request.match_info['field'] assert field_name in {'image', 'logo'}, field_name # double check content = await request_image( request, expected_size=None if field_name == 'image' else LOGO_SIZE) co_id = request['company_id'] co_slug, old_image = await request['conn'].fetchrow_b( 'SELECT slug, :image_field FROM companies WHERE id=:id', image_field=V(field_name), id=co_id) upload_path = Path(co_slug) / 'co' / field_name method = upload_background if field_name == 'image' else upload_logo image_url = await method(content, upload_path=upload_path, settings=request.app['settings']) await request['conn'].execute_b('UPDATE companies SET :set WHERE id=:id', set=V(field_name) == image_url, id=co_id) if old_image: await delete_image(old_image, request.app['settings']) return json_response(status='success')
async def set_event_description_image(request): event_id = await _check_event_permissions(request, check_upcoming=True) content = await request_image(request, expected_size=description_image_size) image = await request['conn'].fetchval( 'SELECT description_image from events WHERE id=$1', event_id) if image: await delete_image(image, request.app['settings']) co_slug, cat_slug, event_slug = await request['conn'].fetchrow( slugs_sql, request['company_id'], event_id) upload_path = Path(co_slug) / cat_slug / event_slug / 'description' image_url = await upload_other( content, upload_path=upload_path, settings=request.app['settings'], req_size=description_image_size, thumb=True, ) async with request['conn'].transaction(): await request['conn'].execute( 'UPDATE events SET description_image=$1 WHERE id=$2', image_url, event_id) await record_action( request, request['session']['user_id'], ActionTypes.edit_event, event_id=event_id, subtype='set-image-description', ) return json_response(status='success')
async def authenticate_token(request): m = await parse_request(request, AuthTokenModel) auth_session = decrypt_json(request.app, m.token, ttl=10) session = await new_session(request) session.update(auth_session) await record_action(request, session['user_id'], ActionTypes.login) return json_response(status='success')
async def category_delete_image(request): m = await parse_request(request, ImageActionModel) # _get_cat_img_path is required to check the category is on the right company await _get_cat_img_path(request) await delete_image(m.image, request.app['settings']) return json_response(status='success')
async def category_set_image(request): m = await parse_request(request, ImageModel) await _check_image_exists(request, m) cat_id = int(request.match_info['cat_id']) await request['conn'].execute( 'UPDATE categories SET image = $1 WHERE id = $2', m.image, cat_id) return json_response(status='success')
async def add(self) -> web.Response: m = await parse_request(self.request, self.model) data = await self.prepare_add_data(m.dict()) try: pk = await self.add_execute(**data) except UniqueViolationError as e: raise self.conflict_exc(e) else: return json_response(status='ok', pk=pk, status_=201)
async def get_payment_method_details(request): payment_method_id = request.match_info['payment_method'] data = await get_stripe_payment_method( payment_method_id=payment_method_id, company_id=request['company_id'], user_id=request['session']['user_id'], app=request.app, conn=request['conn']) return json_response(**data)
async def category_default_image(request): m = await parse_request(request, ImageActionModel) path = await _get_cat_img_path(request) images = await list_images(path, request.app['settings']) if m.image not in images: raise JsonErrors.HTTPBadRequest(message='image does not exist') cat_id = int(request.match_info['cat_id']) await request['conn'].execute('UPDATE categories SET image = $1 WHERE id = $2', m.image, cat_id) return json_response(status='success')
async def switch_highlight(request): event_id = await _check_event_permissions(request) await request['conn'].execute( 'UPDATE events SET highlight=NOT highlight WHERE id=$1', event_id) await record_action(request, request['session']['user_id'], ActionTypes.edit_event, event_id=event_id, subtype='switch-highlight') return json_response(status='ok')
async def email_def_browse(request): results = await request['conn'].fetch( 'SELECT trigger, active FROM email_definitions WHERE company=$1', request['company_id']) lookup = {r['trigger']: r for r in results} return json_response( items=[{ 'trigger': t, 'customised': t in lookup, 'active': lookup[t]['active'] if t in lookup else True, } for t in Triggers])
def successful_login(user, app, headers_=None): auth_session = { 'user_id': user['id'], 'user_role': user['role'], 'last_active': int(time()) } auth_token = encrypt_json(app, auth_session) return json_response(status='success', auth_token=auth_token, user=user, headers_=headers_)
async def clear_email_def(request): trigger = get_trigger(request) r = await request['conn'].execute( 'DELETE FROM email_definitions WHERE trigger=$1 AND company=$2', trigger, request['company_id'], ) if r == 'DELETE 1': return json_response(status='ok') else: raise JsonErrors.HTTPNotFound( message=f'email definition with trigger "{trigger}" not found')
async def set_event_image_existing(request): m = await parse_request(request, ImageModel) if not m.image.startswith(request.app['settings'].s3_domain): raise JsonErrors.HTTPBadRequest(message='image not allowed') await _delete_existing_image(request) event_id = int(request.match_info['id']) await request['conn'].execute('UPDATE events SET image=$1 WHERE id=$2', m.image, event_id) await record_action(request, request['session']['user_id'], ActionTypes.edit_event, event_id=event_id, subtype='set-image-existing') return json_response(status='success')
async def remove_event_secondary_image(request): event_id = await _check_event_permissions(request, check_upcoming=True) image = await request['conn'].fetchval('select secondary_image from events where id=$1', event_id) if image: await delete_image(image, request.app['settings']) async with request['conn'].transaction(): await request['conn'].execute('update events set secondary_image=null where id=$1', event_id) await record_action(request, request['session']['user_id'], ActionTypes.edit_event, event_id=event_id, subtype='remove-image-secondary') return json_response(status='success')
async def switch_user_status(request): user_id = int(request.match_info['pk']) status = await request['conn'].fetchval( 'SELECT status FROM users WHERE id=$1 AND company=$2', user_id, request['company_id'], ) if not status: raise JsonErrors.HTTPNotFound(message='user not found') new_status = 'suspended' if status == 'active' else 'active' await request['conn'].execute('UPDATE users SET status=$1 WHERE id=$2', new_status, user_id) return json_response(new_status=new_status)
async def category_delete_image(request): m = await parse_request(request, ImageModel) await _check_image_exists(request, m) cat_id = int(request.match_info['cat_id']) dft_image = await request['conn'].fetchval( 'SELECT image FROM categories WHERE id=$1', cat_id) if dft_image == m.image: raise JsonErrors.HTTPBadRequest( message='default image may not be be deleted') await delete_image(m.image, request.app['settings']) return json_response(status='success')
async def edit(self, pk) -> web.Response: await self.check_item_permissions(pk) m = await parse_request_ignore_missing(self.request, self.model) data = await self.prepare_edit_data(pk, m.dict(exclude_unset=True)) if not data: raise JsonErrors.HTTPBadRequest(message=f'no data to save') try: await self.edit_execute(pk, **data) except UniqueViolationError as e: raise self.conflict_exc(e) else: return json_response(status='ok')
async def login_with(request): model, siw_method = LOGIN_MODELS[request.match_info['site']] m = await parse_request(request, model) details = await siw_method(m, app=request.app) email = details['email'] r = await request['conn'].fetchrow(LOGIN_USER_SQL, request['company_id'], email) if r: user = dict(r) return successful_login(user, request.app) return json_response( status='invalid', message=f'User with email address "{email}" not found', status_=470)
async def event_tickets(request): event_id = await _check_event_permissions(request) not_admin = request['session']['role'] != 'admin' tickets = [] settings = request.app['settings'] for t in await request['conn'].fetch(event_tickets_sql, event_id): ticket = { 'ticket_id': ticket_id_signed(t['id'], settings), **t, } if not_admin: ticket.pop('guest_email') ticket.pop('buyer_email') tickets.append(ticket) return json_response(tickets=tickets)
async def set_event_image_new(request): content = await request_image(request) await _delete_existing_image(request) event_id = int(request.match_info['id']) co_slug, cat_slug, event_slug = await request['conn'].fetchrow(slugs_sql, request['company_id'], event_id) upload_path = Path(co_slug) / cat_slug / event_slug image_url = await upload_background(content, upload_path, request.app['settings']) await request['conn'].execute('UPDATE events SET image=$1 WHERE id=$2', image_url, event_id) await record_action(request, request['session']['user_id'], ActionTypes.edit_event, event_id=event_id, subtype='set-image-new') return json_response(status='success')
async def category_add_image(request): try: p = await request.post() except ValueError: raise HTTPRequestEntityTooLarge image = p['image'] content = image.file.read() try: check_size_save(content) except ValueError as e: raise JsonErrors.HTTPBadRequest(message=str(e)) upload_path = await _get_cat_img_path(request) await resize_upload(content, upload_path, request.app['settings']) return json_response(status='success')
async def email_def_edit(request): m = await parse_request(request, EmailDefModel) trigger = get_trigger(request) await request['conn'].execute_b(""" INSERT INTO email_definitions AS ed (:values__names) VALUES :values ON CONFLICT (company, trigger) DO UPDATE SET subject=EXCLUDED.subject, title=EXCLUDED.title, body=EXCLUDED.body, active=EXCLUDED.active """, values=Values( trigger=trigger, company=request['company_id'], **m.dict())) return json_response(status='ok')
async def donation_after_prepare(request): donation_option_id = int(request.match_info['don_opt_id']) event_id = int(request.match_info['event_id']) conn = request['conn'] r = await conn.fetchrow( """ SELECT opt.name, opt.amount, cat.id FROM donation_options AS opt JOIN categories AS cat ON opt.category = cat.id WHERE opt.id = $1 AND opt.live AND cat.company = $2 """, donation_option_id, request['company_id'], ) if not r: raise JsonErrors.HTTPBadRequest(message='donation option not found') name, amount, cat_id = r event = await conn.fetchval( 'SELECT 1 FROM events WHERE id=$1 AND category=$2', event_id, cat_id) if not event: raise JsonErrors.HTTPBadRequest( message='event not found on the same category as donation_option') user_id = request['session']['user_id'] action_id = await record_action_id(request, user_id, ActionTypes.donate_prepare, event_id=event_id, donation_option_id=donation_option_id) client_secret = await stripe_payment_intent( user_id=user_id, price_cents=int(amount * 100), description=f'donation to {name} ({donation_option_id}) after booking', metadata={ 'purpose': 'donate', 'event_id': event_id, 'reserve_action_id': action_id, 'user_id': user_id }, company_id=request['company_id'], idempotency_key=f'idempotency-donate-{action_id}', app=request.app, conn=conn, ) return json_response(client_secret=client_secret, action_id=action_id)
async def waiting_list_add(request): event_id = int(request.match_info['id']) conn: BuildPgConnection = request['conn'] user_id = request['session']['user_id'] wl_id = await conn.fetchval( """ insert into waiting_list (event, user_id) values ($1, $2) on conflict (event, user_id) do nothing returning id """, event_id, user_id, ) if wl_id: await request.app['email_actor'].waiting_list_add(wl_id) return json_response(status='ok')