Skip to content

Commit f81cceb

Browse files
Additional web hook for clearing existing appointments (#322)
* Heroku ready (#276) * Get heroku ready * Add yml file * Remove unneeded files * Remove deploy files * Remove check for services being ready * Update logs * Update logging * Updating tc root * Logging for photos * Logging for setting an image * More logging * Images to aws * Remove hash test * Storing images in s3 and tests * Adding secrets * Tests * Add runtime * Fix deploy * Install boto3 * Fix images * Remove end2end * Created method to clear all appointments * linting * socket side ability to take many apts in a single webhook * Can delete apts in mass_apt_webhook * Single endpoint Co-authored-by: Tom Hamilton Stubber <tomhamiltonstubber@gmail.com>
1 parent 4be3d16 commit f81cceb

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed

tcsocket/app/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from .views.appointments import (
1313
appointment_list,
1414
appointment_webhook,
15+
appointment_webhook_clear,
1516
appointment_webhook_delete,
17+
appointment_webhook_mass,
1618
book_appointment,
1719
check_client,
1820
service_list,
@@ -52,9 +54,15 @@ def setup_routes(app):
5254
app.router.add_post(r'/{company}/webhook/contractor', contractor_set, name='webhook-contractor')
5355
app.router.add_post(r'/{company}/webhook/clear-enquiry', clear_enquiry, name='webhook-clear-enquiry')
5456
app.router.add_post(r'/{company}/webhook/appointments/{id:\d+}', appointment_webhook, name='webhook-appointment')
57+
app.router.add_post(
58+
r'/{company}/webhook/appointments/mass', appointment_webhook_mass, name='webhook-appointment-mass'
59+
)
5560
app.router.add_delete(
5661
r'/{company}/webhook/appointments/{id:\d+}', appointment_webhook_delete, name='webhook-appointment-delete'
5762
)
63+
app.router.add_delete(
64+
r'/{company}/webhook/appointments/clear', appointment_webhook_clear, name='webhook-appointment-clear'
65+
)
5866

5967
app.router.add_get(r'/{company}/contractors', contractor_list, name='contractor-list')
6068
app.router.add_get(r'/{company}/contractors/{id:\d+}', contractor_get, name='contractor-get')

tcsocket/app/views/appointments.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,67 @@ async def appointment_webhook(request):
7171
return json_response(request, status='success')
7272

7373

74+
async def appointment_webhook_mass(request):
75+
conn = await request['conn_manager'].get_connection()
76+
data = await request.json()
77+
if data['_request_time']:
78+
del data['_request_time']
79+
for apt_id, apt in data.items():
80+
if apt['ss_method'] == 'POST':
81+
appointment: AppointmentModel = apt
82+
83+
v = await conn.execute(select([ser_c.company]).where(ser_c.id == appointment['service_id']))
84+
r = await v.first()
85+
if r and r.company != request['company'].id:
86+
raise HTTPConflictJson(
87+
status='service conflict',
88+
details=f'service {appointment["service_id"]} already exists'
89+
' and is associated with another company',
90+
)
91+
92+
service_insert_update = dict(
93+
name=appointment['service_name'],
94+
colour=appointment['colour'],
95+
extra_attributes=[
96+
ea.dict(exclude={'sort_index'})
97+
for ea in sorted(appointment['extra_attributes'], key=attrgetter('sort_index'))
98+
],
99+
)
100+
101+
await conn.execute(
102+
pg_insert(sa_services)
103+
.values(id=appointment['service_id'], company=request['company'].id, **service_insert_update)
104+
.on_conflict_do_update(
105+
index_elements=[ser_c.id], where=ser_c.id == appointment['service_id'], set_=service_insert_update,
106+
)
107+
)
108+
apt_insert_keys = [
109+
'attendees_max',
110+
'attendees_count',
111+
'attendees_current_ids',
112+
'start',
113+
'finish',
114+
'price',
115+
'location',
116+
]
117+
118+
apt_insert_update = {key: value for key, value in apt.items() if key in apt_insert_keys}
119+
apt_insert_update['topic'] = appointment['appointment_topic']
120+
121+
await conn.execute(
122+
pg_insert(sa_appointments)
123+
.values(id=apt_id, service=appointment['service_id'], **apt_insert_update)
124+
.on_conflict_do_update(index_elements=[apt_c.id], where=apt_c.id == apt_id, set_=apt_insert_update,)
125+
)
126+
elif apt['ss_method'] == 'DELETE':
127+
await conn.execute(
128+
sa_appointments.delete().where(and_(apt_c.id == apt_id, ser_c.company == request['company'].id))
129+
)
130+
else:
131+
return
132+
return json_response(request, status='success')
133+
134+
74135
async def appointment_webhook_delete(request):
75136
apt_id = request.match_info['id']
76137
conn = await request['conn_manager'].get_connection()
@@ -80,6 +141,13 @@ async def appointment_webhook_delete(request):
80141
return json_response(request, status='success' if v.rowcount else 'appointment not found')
81142

82143

144+
async def appointment_webhook_clear(request):
145+
conn = await request['conn_manager'].get_connection()
146+
v = await conn.execute(sa_appointments.delete().where(ser_c.company == request['company'].id))
147+
r = await conn.execute(sa_services.delete().where(ser_c.company == request['company'].id))
148+
return json_response(request, status='success' if r.rowcount or v.rowcount else 'appointments not found')
149+
150+
83151
APT_LIST_FIELDS = (
84152
apt_c.id,
85153
apt_c.topic,

tests/test_appointments_set.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,126 @@ async def test_delete_old_appointments(db_conn, company, settings):
170170

171171
assert {(1, 1), (3, 3)} == await select_set(db_conn, sa_appointments.c.id, sa_appointments.c.service)
172172
assert {(1,), (3,)} == await select_set(db_conn, sa_services.c.id)
173+
174+
175+
async def test_clear_apts(cli, db_conn, company):
176+
await create_appointment(db_conn, company, appointment_extra={'id': 1})
177+
for i in range(10):
178+
await create_appointment(
179+
db_conn,
180+
company,
181+
create_service=False,
182+
appointment_extra=dict(
183+
id=i + 2,
184+
start=datetime(2032, 1, 1, 12, 0, 0) + timedelta(days=i + 1),
185+
finish=datetime(2032, 1, 1, 13, 0, 0) + timedelta(days=i + 1),
186+
),
187+
)
188+
189+
assert 11 == await count(db_conn, sa_appointments)
190+
assert 1 == await count(db_conn, sa_services)
191+
192+
url = cli.server.app.router['webhook-appointment-clear'].url_for(company='thepublickey')
193+
r = await signed_request(cli, url, method_='DELETE')
194+
assert r.status == 200
195+
assert {'status': 'success'} == await r.json()
196+
197+
assert 0 == await count(db_conn, sa_appointments)
198+
assert 0 == await count(db_conn, sa_services)
199+
200+
201+
async def test_mass_apts(cli, db_conn, company):
202+
await create_appointment(db_conn, company, appointment_extra={'id': 1})
203+
assert 1 == await count(db_conn, sa_appointments)
204+
assert 1 == await count(db_conn, sa_services)
205+
206+
data = dict()
207+
for i in range(10):
208+
data[str(i + 2)] = dict(
209+
service_id=1,
210+
service_name='test service',
211+
extra_attributes=[],
212+
colour='#000000',
213+
appointment_topic='testing appointment',
214+
attendees_max=42,
215+
attendees_count=4,
216+
attendees_current_ids=[1, 2, 3],
217+
start=str(datetime(2032, 1, 1, 12, 0, 0) + timedelta(days=i + 1)),
218+
finish=str(datetime(2032, 1, 1, 13, 0, 0) + timedelta(days=i + 1)),
219+
price=123.45,
220+
location='Whatever',
221+
ss_method='POST'
222+
)
223+
url = cli.server.app.router['webhook-appointment-mass'].url_for(company='thepublickey')
224+
r = await signed_request(cli, url, **data)
225+
assert r.status == 200
226+
assert {'status': 'success'} == await r.json()
227+
228+
assert 11 == await count(db_conn, sa_appointments)
229+
assert 1 == await count(db_conn, sa_services)
230+
231+
data = dict()
232+
for i in range(9):
233+
data[str(i + 2)] = dict(
234+
service_id=1,
235+
service_name='test service',
236+
extra_attributes=[],
237+
colour='#000000',
238+
appointment_topic='testing appointment',
239+
attendees_max=42,
240+
attendees_count=4,
241+
attendees_current_ids=[1, 2, 3],
242+
start=str(datetime(2032, 1, 1, 12, 0, 0) + timedelta(days=i + 1)),
243+
finish=str(datetime(2032, 1, 1, 13, 0, 0) + timedelta(days=i + 1)),
244+
price=123.45,
245+
location='Whatever',
246+
ss_method='POST'
247+
)
248+
data['10'] = {'ss_method': 'DELETE'}
249+
data['11'] = {'ss_method': 'DELETE'}
250+
url = cli.server.app.router['webhook-appointment-mass'].url_for(company='thepublickey')
251+
r = await signed_request(cli, url, **data)
252+
assert r.status == 200
253+
assert {'status': 'success'} == await r.json()
254+
255+
assert 9 == await count(db_conn, sa_appointments)
256+
assert 1 == await count(db_conn, sa_services)
257+
258+
259+
async def test_mass_apts_and_services(cli, db_conn, company):
260+
await create_appointment(db_conn, company, appointment_extra={'id': 1})
261+
assert 1 == await count(db_conn, sa_appointments)
262+
assert 1 == await count(db_conn, sa_services)
263+
264+
data = dict()
265+
for i in range(10):
266+
data[str(i + 2)] = dict(
267+
service_id=i + 2,
268+
service_name='test service',
269+
extra_attributes=[],
270+
colour='#000000',
271+
appointment_topic='testing appointment',
272+
attendees_max=42,
273+
attendees_count=4,
274+
attendees_current_ids=[1, 2, 3],
275+
start=str(datetime(2032, 1, 1, 12, 0, 0) + timedelta(days=i + 1)),
276+
finish=str(datetime(2032, 1, 1, 13, 0, 0) + timedelta(days=i + 1)),
277+
price=123.45,
278+
location='Whatever',
279+
ss_method='POST'
280+
)
281+
url = cli.server.app.router['webhook-appointment-mass'].url_for(company='thepublickey')
282+
r = await signed_request(cli, url, **data)
283+
assert r.status == 200
284+
assert {'status': 'success'} == await r.json()
285+
286+
assert 11 == await count(db_conn, sa_appointments)
287+
assert 11 == await count(db_conn, sa_services)
288+
289+
url = cli.server.app.router['webhook-appointment-clear'].url_for(company='thepublickey')
290+
r = await signed_request(cli, url, method_='DELETE')
291+
assert r.status == 200
292+
assert {'status': 'success'} == await r.json()
293+
294+
assert 0 == await count(db_conn, sa_appointments)
295+
assert 0 == await count(db_conn, sa_services)

0 commit comments

Comments
 (0)