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

POST to the share address for a note returns a token and a full URL containing it, with status 201. GET on the shared address looks the token up in an in-memory mapping and returns the note as JSON, or 404 if the token or note is missing. Before this run, the only read paths were the list and per-id GET routes.

  • 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

  • Share links never expire and cannot be revoked. Expiry after seven days is the next step you asked for and is not in this run.
  • The test runner reported 7 passed for the command pytest -q. The four share tests are the new ones in test_app.py.
  • The changed files app.py, test_app.py and README.md are recorded as not attributable to the 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

share_note assigns the token from a value that was masked before this message was sent, and the link is built from that same value. read_shared_note checks only that the token is in the shares mapping, with no other check. Whether the link is safe depends entirely on how the token is generated, which the message does not show.

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

delete_note pops only the note from notes. The shares mapping is not touched, so every stale token stays as a key. The test test_share_link_dies_with_note shows that a link returns 404 after its note is deleted, because the note lookup returns nothing.

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