Skip to content

Commit 633d1e9

Browse files
committed
add delete endpoint to remove a short link
1 parent 16b9929 commit 633d1e9

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ Returns a QR code image encoding the short URL. Useful for sharing links in prin
133133
PNG by default; pass `fmt=svg` for a crisp, scalable vector you can drop into print or the web.
134134
`scale` sets the pixel size of each module (1-40, default 10) and `border` the quiet zone width (0-20, default 4).
135135

136+
### Delete
137+
138+
```http
139+
DELETE /api/links/{code} -> 204 No Content
140+
```
141+
142+
Removes a short link by its code or custom alias. Returns 404 if nothing matches. The code lookup is case-insensitive, same as the other endpoints.
143+
136144
### Redirect
137145

138146
```http

app/routers/api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ async def get_qr_code(
168168
headers = {"Cache-Control": "public, max-age=86400"}
169169
return StreamingResponse(buf, media_type=media_type, headers=headers)
170170

171+
@router.delete("/api/links/{code}", status_code=204)
172+
async def delete_link(code: str, session: AsyncSession = Depends(get_session)):
173+
link = await find_link(session, code)
174+
if not link:
175+
raise HTTPException(status_code=404, detail="Link not found")
176+
await session.delete(link)
177+
await session.commit()
178+
171179
@router.get("/{code}")
172180
async def redirect_to_url(code: str, session: AsyncSession = Depends(get_session)):
173181
link = await find_link(session, code)

tests/test_api.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,3 +662,31 @@ async def test_stats_page_custom_alias_lookup_case_insensitive(client):
662662
assert res.status_code == 200
663663
assert "/Gh-Link" in res.text
664664

665+
666+
667+
@pytest.mark.asyncio
668+
async def test_delete_link(client):
669+
res = await client.post("/api/shorten", json={"url": "https://example.com"})
670+
code = res.json()["short_code"]
671+
672+
res = await client.delete(f"/api/links/{code}")
673+
assert res.status_code == 204
674+
675+
res = await client.get(f"/{code}", follow_redirects=False)
676+
assert res.status_code == 404
677+
678+
679+
@pytest.mark.asyncio
680+
async def test_delete_link_case_insensitive(client):
681+
with patch("app.routers.api.generate_short_code", return_value="AbC123"):
682+
res = await client.post("/api/shorten", json={"url": "https://example.com"})
683+
assert res.status_code == 201
684+
685+
res = await client.delete("/api/links/abc123")
686+
assert res.status_code == 204
687+
688+
689+
@pytest.mark.asyncio
690+
async def test_delete_link_not_found(client):
691+
res = await client.delete("/api/links/nope")
692+
assert res.status_code == 404

0 commit comments

Comments
 (0)