BuildGist

A real run from BuildGist’s demo project, shown as BuildGist explained it. What happened is BuildGist’s own record of the run. The explanation below it was written by AI.

Add share links to notes. POST /notes/<id>/share should return a link anyone can open to read that…

notes · Claude Code · 3 days ago

What happened recorded by BuildGist

The developer asked
Add share links to notes. POST /notes/<id>/share should return a link anyone can open to read that note. Write tests and run them. After this I want links that expire after 7 days, don't start that yet.
Files changed
3 files +46 −2
Show files
  • modified test_app.py +26 −0
  • modified app.py +19 −1
  • modified README.md +1 −1
Tests run
  • .venv/bin/python -m pytest -q passed 7 passed

Written by AI. Check important behavior against the code and tests.

Added note share links

Added a share endpoint that returns a link anyone can open to read a note, with tests.

What changed

Each note can now be given a share link. Anyone who opens that link can read the note's contents. Before this run, notes could only be read through the notes list or by note number. The links do not expire yet; expiry is the next step you asked for.

  • Share links for notes

    Each note can now get a share link: a POST to its share address returns a token and a full link, and opening that link returns the note as JSON.

    Show code
    app.py
    47            abort(404)
    48        return "", 204
    49
    50    @app.post("/notes/<int:note_id>/share")
    51    def share_note(note_id: int):
    52        if note_id not in notes:
    53            abort(404)
    54        token = [redacted])
    55        shares[token] = note_id
    56        url = url_for("read_shared_note", token=[redacted], _external=True)
    57        return jsonify({"token": token, "url": url}), 201
    58
    app.py
    56        url = url_for("read_shared_note", token=[redacted], _external=True)
    57        return jsonify({"token": token, "url": url}), 201
    58
    59    @app.get("/shared/<token>")
    60    def read_shared_note(token: str):
    61        note = notes.get(shares.get(token))
    62        if note is None:
    63            abort(404)
    64        return jsonify(note)
    65
    66    return app
    app.py
    10def create_app() -> Flask:
    11    app = Flask(__name__)
    12    notes: dict[int, dict] = {}
    13    shares: dict[str, int] = {}  # token -> note id
    14    next_id = 1
    15
    16    @app.get("/notes")
  • Tests for share links

    Four new tests cover reading a note through its link, sharing a missing note, an unknown token, and a link that stops working after its note is deleted.

    Show code
    test_app.py
    26    assert client.get(f"/notes/{note_id}").status_code == 404
    27
    28
    29def test_share_link_reads_note(client):
    30    note_id = client.post("/notes", json={"title": "Recipe", "body": "flour"}).get_json()["id"]
    31    shared = client.post(f"/notes/{note_id}/share")
    32    assert shared.status_code == 201
    33    url = shared.get_json()["url"]
    34    assert url.startswith("http://localhost/shared/")
    35    opened = client.get(url)
    36    assert opened.status_code == 200
    37    assert opened.get_json()["body"] == "flour"
    test_app.py
    45    assert client.get("/shared/not-a-token").status_code == 404
    46
    47
    48def test_share_link_dies_with_note(client):
    49    note_id = client.post("/notes", json={"title": "Temp"}).get_json()["id"]
    50    url = client.post(f"/notes/{note_id}/share").get_json()["url"]
    51    client.delete(f"/notes/{note_id}")
    52    assert client.get(url).status_code == 404
  • Endpoint documentation

    The README's endpoint list now includes the share endpoint and the shared-note endpoint, alongside the existing note endpoints.

    Show code
    README.md
    6    .venv/bin/python -m flask --app app run      # http://127.0.0.1:5000/notes
    7    .venv/bin/python -m pytest -q                # tests
    8
    9Endpoints: `GET /notes`, `POST /notes` (`{"title", "body"}`), `GET /notes/<id>`, `DELETE /notes/<id>`, `POST /notes/<id>/share` (returns `{"token", "url"}`), `GET /shared/<token>`.

What this means

  • The link has no expiry or way to cancel it yet. You asked for expiry as a later step, so this is expected.
  • The test run reported all seven tests passing. The seven include the four new share tests.
  • BuildGist cannot tell who made the edits to the three changed files. They may have been made by you or another tool, not the coding agent.

Concerns

The line that creates each share token is masked in the shown code, so it cannot be checked whether the token is hard to guess, and anyone who guesses one can read the note. Medium

Each share link has a secret code in it. The part of the code that makes that code is hidden in what was shown, so it cannot be confirmed that the code is hard to guess. If it were easy to guess, someone could read notes they were never given.

Show evidence
app.py
47            abort(404)
48        return "", 204
49
50    @app.post("/notes/<int:note_id>/share")
51    def share_note(note_id: int):
52        if note_id not in notes:
53            abort(404)
54        token = [redacted])
55        shares[token] = note_id
56        url = url_for("read_shared_note", token=[redacted], _external=True)
57        return jsonify({"token": token, "url": url}), 201
58
app.py
56        url = url_for("read_shared_note", token=[redacted], _external=True)
57        return jsonify({"token": token, "url": url}), 201
58
59    @app.get("/shared/<token>")
60    def read_shared_note(token: str):
61        note = notes.get(shares.get(token))
62        if note is None:
63            abort(404)
64        return jsonify(note)
65
66    return app

Share tokens are never removed from the shares mapping when their note is deleted, so stale entries accumulate. Low

More

When a note is deleted, its share codes are not cleared out of the lookup table. Reading one of those old links still gives a not-found answer, but the table keeps growing.

Show evidence
app.py
10def create_app() -> Flask:
11    app = Flask(__name__)
12    notes: dict[int, dict] = {}
13    shares: dict[str, int] = {}  # token -> note id
14    next_id = 1
15
16    @app.get("/notes")
test_app.py
45    assert client.get("/shared/not-a-token").status_code == 404
46
47
48def test_share_link_dies_with_note(client):
49    note_id = client.post("/notes", json={"title": "Temp"}).get_json()["id"]
50    url = client.post(f"/notes/{note_id}/share").get_json()["url"]
51    client.delete(f"/notes/{note_id}")
52    assert client.get(url).status_code == 404