Skip to content

Commit 02c4323

Browse files
Tim020claude
andcommitted
Fix HTTP DELETE endpoints to use query parameters instead of request body (Issue #809)
Updated DELETE endpoints to follow HTTP best practices by using query parameters instead of request bodies. This fixes compatibility issues with HTTP clients and proxies that may strip bodies from DELETE requests. Backend changes: - Updated 8 DELETE methods across 6 controllers to use self.get_argument() - Added explicit int() conversion with try/except for proper error handling - Returns 400 error for invalid (non-numeric) IDs Frontend changes: - Updated 9 DELETE API calls to use URLSearchParams - Follows existing pattern from DELETE_SCRIPT_REVISION All 200 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a076b1e commit 02c4323

File tree

8 files changed

+118
-45
lines changed

8 files changed

+118
-45
lines changed

client/src/store/modules/script.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,15 @@ export default {
187187
}
188188
},
189189
async DELETE_CUE(context, cue) {
190-
const response = await fetch(`${makeURL('/api/v1/show/cues')}`, {
190+
const searchParams = new URLSearchParams({
191+
cueId: cue.cueId,
192+
lineId: cue.lineId,
193+
});
194+
const response = await fetch(`${makeURL('/api/v1/show/cues')}?${searchParams}`, {
191195
method: 'DELETE',
192196
headers: {
193197
'Content-Type': 'application/json',
194198
},
195-
body: JSON.stringify(cue),
196199
});
197200
if (response.ok) {
198201
context.dispatch('LOAD_CUES');
@@ -265,12 +268,14 @@ export default {
265268
}
266269
},
267270
async DELETE_STAGE_DIRECTION_STYLE(context, styleId) {
268-
const response = await fetch(`${makeURL('/api/v1/show/script/stage_direction_styles')}`, {
271+
const searchParams = new URLSearchParams({
272+
id: styleId,
273+
});
274+
const response = await fetch(`${makeURL('/api/v1/show/script/stage_direction_styles')}?${searchParams}`, {
269275
method: 'DELETE',
270276
headers: {
271277
'Content-Type': 'application/json',
272278
},
273-
body: JSON.stringify({ id: styleId }),
274279
});
275280
if (response.ok) {
276281
context.dispatch('GET_STAGE_DIRECTION_STYLES');

client/src/store/modules/show.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ export default {
9898
}
9999
},
100100
async DELETE_CAST_MEMBER(context, castID) {
101-
const response = await fetch(`${makeURL('/api/v1/show/cast')}`, {
101+
const searchParams = new URLSearchParams({
102+
id: castID,
103+
});
104+
const response = await fetch(`${makeURL('/api/v1/show/cast')}?${searchParams}`, {
102105
method: 'DELETE',
103106
headers: {
104107
'Content-Type': 'application/json',
105108
},
106-
body: JSON.stringify({ id: castID }),
107109
});
108110
if (response.ok) {
109111
context.dispatch('GET_CAST_LIST');
@@ -155,12 +157,14 @@ export default {
155157
}
156158
},
157159
async DELETE_CHARACTER(context, characterID) {
158-
const response = await fetch(`${makeURL('/api/v1/show/character')}`, {
160+
const searchParams = new URLSearchParams({
161+
id: characterID,
162+
});
163+
const response = await fetch(`${makeURL('/api/v1/show/character')}?${searchParams}`, {
159164
method: 'DELETE',
160165
headers: {
161166
'Content-Type': 'application/json',
162167
},
163-
body: JSON.stringify({ id: characterID }),
164168
});
165169
if (response.ok) {
166170
context.dispatch('GET_CHARACTER_LIST');
@@ -213,12 +217,14 @@ export default {
213217
}
214218
},
215219
async DELETE_CHARACTER_GROUP(context, characterGroupID) {
216-
const response = await fetch(`${makeURL('/api/v1/show/character/group')}`, {
220+
const searchParams = new URLSearchParams({
221+
id: characterGroupID,
222+
});
223+
const response = await fetch(`${makeURL('/api/v1/show/character/group')}?${searchParams}`, {
217224
method: 'DELETE',
218225
headers: {
219226
'Content-Type': 'application/json',
220227
},
221-
body: JSON.stringify({ id: characterGroupID }),
222228
});
223229
if (response.ok) {
224230
context.dispatch('GET_CHARACTER_GROUP_LIST');
@@ -270,12 +276,14 @@ export default {
270276
}
271277
},
272278
async DELETE_ACT(context, actID) {
273-
const response = await fetch(`${makeURL('/api/v1/show/act')}`, {
279+
const searchParams = new URLSearchParams({
280+
id: actID,
281+
});
282+
const response = await fetch(`${makeURL('/api/v1/show/act')}?${searchParams}`, {
274283
method: 'DELETE',
275284
headers: {
276285
'Content-Type': 'application/json',
277286
},
278-
body: JSON.stringify({ id: actID }),
279287
});
280288
if (response.ok) {
281289
context.dispatch('GET_ACT_LIST');
@@ -344,12 +352,14 @@ export default {
344352
}
345353
},
346354
async DELETE_SCENE(context, sceneID) {
347-
const response = await fetch(`${makeURL('/api/v1/show/scene')}`, {
355+
const searchParams = new URLSearchParams({
356+
id: sceneID,
357+
});
358+
const response = await fetch(`${makeURL('/api/v1/show/scene')}?${searchParams}`, {
348359
method: 'DELETE',
349360
headers: {
350361
'Content-Type': 'application/json',
351362
},
352-
body: JSON.stringify({ id: sceneID }),
353363
});
354364
if (response.ok) {
355365
context.dispatch('GET_SCENE_LIST');
@@ -403,12 +413,14 @@ export default {
403413
}
404414
},
405415
async DELETE_CUE_TYPE(context, cueTypeID) {
406-
const response = await fetch(`${makeURL('/api/v1/show/cues/types')}`, {
416+
const searchParams = new URLSearchParams({
417+
id: cueTypeID,
418+
});
419+
const response = await fetch(`${makeURL('/api/v1/show/cues/types')}?${searchParams}`, {
407420
method: 'DELETE',
408421
headers: {
409422
'Content-Type': 'application/json',
410423
},
411-
body: JSON.stringify({ id: cueTypeID }),
412424
});
413425
if (response.ok) {
414426
context.dispatch('GET_CUE_TYPES');
@@ -493,12 +505,14 @@ export default {
493505
}
494506
},
495507
async DELETE_MICROPHONE(context, microphoneId) {
496-
const response = await fetch(`${makeURL('/api/v1/show/microphones')}`, {
508+
const searchParams = new URLSearchParams({
509+
id: microphoneId,
510+
});
511+
const response = await fetch(`${makeURL('/api/v1/show/microphones')}?${searchParams}`, {
497512
method: 'DELETE',
498513
headers: {
499514
'Content-Type': 'application/json',
500515
},
501-
body: JSON.stringify({ id: microphoneId }),
502516
});
503517
if (response.ok) {
504518
context.dispatch('GET_MICROPHONE_LIST');

server/controllers/api/show/acts.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,20 @@ async def delete(self):
175175
show: Show = session.get(Show, show_id)
176176
if show:
177177
self.requires_role(show, Role.WRITE)
178-
data = escape.json_decode(self.request.body)
179178

180-
act_id = data.get("id", None)
181-
if not act_id:
179+
act_id_str = self.get_argument("id", None)
180+
if not act_id_str:
182181
self.set_status(400)
183182
await self.finish({"message": "ID missing"})
184183
return
185184

185+
try:
186+
act_id = int(act_id_str)
187+
except ValueError:
188+
self.set_status(400)
189+
await self.finish({"message": "Invalid ID"})
190+
return
191+
186192
entry: Act = session.get(Act, act_id)
187193
if entry:
188194
if entry.previous_act and entry.next_act:

server/controllers/api/show/cast.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,20 @@ async def delete(self):
132132
show = session.get(Show, show_id)
133133
if show:
134134
self.requires_role(show, Role.WRITE)
135-
data = escape.json_decode(self.request.body)
136135

137-
cast_id = data.get("id", None)
138-
if not cast_id:
136+
cast_id_str = self.get_argument("id", None)
137+
if not cast_id_str:
139138
self.set_status(400)
140139
await self.finish({"message": "ID missing"})
141140
return
142141

142+
try:
143+
cast_id = int(cast_id_str)
144+
except ValueError:
145+
self.set_status(400)
146+
await self.finish({"message": "Invalid ID"})
147+
return
148+
143149
entry = session.get(Cast, cast_id)
144150
if entry:
145151
session.delete(entry)

server/controllers/api/show/characters.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,20 @@ async def delete(self):
144144
show: Show = session.get(Show, show_id)
145145
if show:
146146
self.requires_role(show, Role.WRITE)
147-
data = escape.json_decode(self.request.body)
148147

149-
character_id = data.get("id", None)
150-
if not character_id:
148+
character_id_str = self.get_argument("id", None)
149+
if not character_id_str:
151150
self.set_status(400)
152151
await self.finish({"message": "ID missing"})
153152
return
154153

154+
try:
155+
character_id = int(character_id_str)
156+
except ValueError:
157+
self.set_status(400)
158+
await self.finish({"message": "Invalid ID"})
159+
return
160+
155161
entry: Character = session.get(Character, character_id)
156162
if entry:
157163
session.delete(entry)
@@ -303,14 +309,20 @@ async def delete(self):
303309
show: Show = session.get(Show, show_id)
304310
if show:
305311
self.requires_role(show, Role.WRITE)
306-
data = escape.json_decode(self.request.body)
307312

308-
character_group_id = data.get("id", None)
309-
if not character_group_id:
313+
character_group_id_str = self.get_argument("id", None)
314+
if not character_group_id_str:
310315
self.set_status(400)
311316
await self.finish({"message": "ID missing"})
312317
return
313318

319+
try:
320+
character_group_id = int(character_group_id_str)
321+
except ValueError:
322+
self.set_status(400)
323+
await self.finish({"message": "Invalid ID"})
324+
return
325+
314326
entry: CharacterGroup = session.get(CharacterGroup, character_group_id)
315327
if entry:
316328
session.delete(entry)

server/controllers/api/show/cues.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,20 @@ async def delete(self):
138138
show: Show = session.get(Show, show_id)
139139
if show:
140140
self.requires_role(show, Role.WRITE)
141-
data = escape.json_decode(self.request.body)
142141

143-
cue_type_id = data.get("id", None)
144-
if not cue_type_id:
142+
cue_type_id_str = self.get_argument("id", None)
143+
if not cue_type_id_str:
145144
self.set_status(400)
146145
await self.finish({"message": "ID missing"})
147146
return
148147

148+
try:
149+
cue_type_id = int(cue_type_id_str)
150+
except ValueError:
151+
self.set_status(400)
152+
await self.finish({"message": "Invalid ID"})
153+
return
154+
149155
entry: CueType = session.get(CueType, cue_type_id)
150156
if entry:
151157
session.delete(entry)
@@ -413,24 +419,36 @@ async def delete(self):
413419
)
414420
return
415421

416-
data = escape.json_decode(self.request.body)
417-
418-
cue_id: int = data.get("cueId")
419-
if not cue_id:
422+
cue_id_str = self.get_argument("cueId", None)
423+
if not cue_id_str:
420424
self.set_status(400)
421425
await self.finish({"message": "Cue ID missing"})
422426
return
423427

428+
try:
429+
cue_id = int(cue_id_str)
430+
except ValueError:
431+
self.set_status(400)
432+
await self.finish({"message": "Invalid Cue ID"})
433+
return
434+
424435
cue = session.get(Cue, cue_id)
425436
cue_type = session.get(CueType, cue.cue_type_id)
426437
self.requires_role(cue_type, Role.WRITE)
427438

428-
line_id: int = data.get("lineId")
429-
if not line_id:
439+
line_id_str = self.get_argument("lineId", None)
440+
if not line_id_str:
430441
self.set_status(400)
431442
await self.finish({"message": "Line ID missing"})
432443
return
433444

445+
try:
446+
line_id = int(line_id_str)
447+
except ValueError:
448+
self.set_status(400)
449+
await self.finish({"message": "Invalid Line ID"})
450+
return
451+
434452
association_object = session.get(
435453
CueAssociation,
436454
{"revision_id": revision.id, "line_id": line_id, "cue_id": cue_id},

server/controllers/api/show/microphones.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,20 @@ async def delete(self):
155155
show: Show = session.get(Show, show_id)
156156
if show:
157157
self.requires_role(show, Role.WRITE)
158-
data = escape.json_decode(self.request.body)
159158

160-
microphone_id = data.get("id", None)
161-
if not microphone_id:
159+
microphone_id_str = self.get_argument("id", None)
160+
if not microphone_id_str:
162161
self.set_status(400)
163162
await self.finish({"message": "ID missing"})
164163
return
165164

165+
try:
166+
microphone_id = int(microphone_id_str)
167+
except ValueError:
168+
self.set_status(400)
169+
await self.finish({"message": "Invalid ID"})
170+
return
171+
166172
entry: Microphone = session.get(Microphone, microphone_id)
167173
if entry:
168174
session.delete(entry)

server/controllers/api/show/scenes.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,20 @@ async def delete(self):
109109
show: Show = session.get(Show, show_id)
110110
if show:
111111
self.requires_role(show, Role.WRITE)
112-
data = escape.json_decode(self.request.body)
113112

114-
scene_id = data.get("id", None)
115-
if not scene_id:
113+
scene_id_str = self.get_argument("id", None)
114+
if not scene_id_str:
116115
self.set_status(400)
117116
await self.finish({"message": "ID missing"})
118117
return
119118

119+
try:
120+
scene_id = int(scene_id_str)
121+
except ValueError:
122+
self.set_status(400)
123+
await self.finish({"message": "Invalid ID"})
124+
return
125+
120126
entry: Scene = session.get(Scene, scene_id)
121127
if entry:
122128
if entry.previous_scene and entry.next_scene:

0 commit comments

Comments
 (0)