Repository navigation
feat: paste links and removed-paste lifecycle in the API - #71
Conversation
Every paste in the API now carries url, the browser link people share, and raw_url, the exact stored bytes, as absolute URLs. The CLI and scripts can print or open them without rebuilding paths from the server address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6
Removed pastes are their own collection, for the personal workspace and for any joined workspace: GET /api/v1/pastes/deleted POST /api/v1/pastes/deleted/:id/restore DELETE /api/v1/pastes/deleted/:id plus the same under /api/v1/workspaces/:workspace_id. Listings carry the removal reason, when it happened and when the paste is purged, never the content. Restore and permanent delete follow the same rules as the Deleted tab, and answer 404 alike for an unknown id, another member's paste and an expired recovery window, so they never confirm a paste exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6
Include reference_url in the content-free removed-paste projection. Extend the API regression to assert the stored reference is returned while all content encoding fields remain absent. The regression failed before the fix; 563 ExUnit tests and Credo pass afterward.
|
Oracle review complete: signed off after resolving the missing reference_url in deleted-list metadata. The removed-paste projection now selects the stored reference while leaving content unloaded. Extended the API regression to verify the exact reference and absence of data/data_base64/data_encoding. The regression failed before the source fix (nil instead of the stored URL) and passes afterward. Final validation: mix precommit (563 ExUnit tests plus JS tests) and mix credo passed. The existing lifecycle API routes remain in /api/v1 with [:api, :require_authenticated_api]; no authentication or route changes were needed for this fix. Fix pushed without rewriting published history. Review session: https://ampcode.com/threads/T-01a12594-e2f1-75be-b343-9239564f5c84 Post-push CI is green: memory/local/S3 Elixir tests, Rust tests, formatting/linting, and production-container build passed. Release/publication jobs skipped as expected. |
…es (#72) ## Summary The second of two stacked PRs. Stacked on #71, whose endpoints it uses. It brings the Rust client and the CLI up to the redesigned paste model. **Client (`textbin-client`)** - Create takes a `CreateOptions` struct and sends `title` and `reference_url` along with syntax, expiry and visibility. - Pastes carry their id, title, reference link, share URL, raw URL and timestamps. All of them are optional, so responses from older servers still decode. - New calls: `list_removed_pastes`, `restore_paste` and `purge_paste`. - Pastes serialize the way the server represents them: text in `data`, and binary content as base64. **CLI (`textbin`)** - `create --title … --reference …` (alias `--ref`) prints the server's share link. - `--json` on `create`, `show`, `delete`, `deleted` and `restore` prints one line of JSON for scripts. This covers the paste commands in RFD 3's "machine-readable `--json`" item. The `auth` commands are not covered yet. - `delete` tells you how to restore the paste. `delete --permanent` deletes it for good, including one that was already deleted. - `deleted` lists restorable pastes with their reason, purge date and title. - `restore <id>` brings a deleted or expired paste back. ## Test plan - [x] `cargo fmt --check`, `cargo clippy --workspace --all-features -- -D warnings` - [x] `cargo test --workspace`: 61 tests, 0 failures. New tests cover create query params, decoding links, JSON serialization of text and binary pastes, the deleted-collection requests against a stub server, `deleted` row formatting and argument parsing. - [x] End to end against a local server: created a paste with a title and reference, then ran `show --json`, `delete`, `deleted`, `restore`, `delete --permanent`, and a restore of the purged paste, which returned 404. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6 --- _Generated by [Claude Code](https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6)_ --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Darwin D. Wu <darwin67@users.noreply.github.com>
## Summary
RFD 3 asks for an OpenAPI document that is versioned with the server and
checked for drift in CI. The repo didn't have one. This PR adds the
document and the checks that keep it accurate.
**New dependency:** `open_api_spex` 3.22 (MPL-2.0), the standard Phoenix
library for OpenAPI. The spec is written in Elixir, and the library also
provides the JSON export and the schema casting the tests use. It has no
open security advisories.
**Commits**
1. **`feat`: the document.**
- `TextbinWeb.ApiSpec` describes all 21 operations:
- Token creation and identity.
- Organizations, workspaces, the audit log and workspace recovery.
- Paste create, list, show and delete.
- The removed-paste lifecycle, under both the personal and the
workspace-scoped routes.
- Raw paste content.
- `TextbinWeb.ApiSpec.Schemas` holds the request and response schemas.
- `mix openapi.export` writes `priv/openapi.json`, the copy for client
generation, such as the TypeScript SDK the RFD mentions. It also puts
contract changes in front of reviewers.
- Tests fail if:
- an `/api/v1` route has no operation in the document, or the document
has an operation with no route;
- a schema reference doesn't resolve;
- `priv/openapi.json` is out of date.
2. **`test`: contract tests.**
- `assert_api_response(conn, operation_id)` checks a real response
against the operation it implements. The status must be one the document
lists, and a JSON body must match its schema.
- Response schemas are closed (`additionalProperties: false`). An
undocumented field therefore fails a test, just like a missing or
mistyped one.
- Every operation is exercised for its success response and its main
errors.
- The two RFD 3 items this PR and #71 complete are ticked.
**How drift gets caught.** These are ordinary `mix test` tests, so they
already run in every CI storage matrix job. Changing a JSON view, a
route, or the document without updating the others fails CI. To check
the tests catch something, I added an undocumented `surprise` field to
`PasteJSON` and changed `title` to a number. Each change failed the
tests with a readable message naming the operation and the field.
**When you change the API:** update `TextbinWeb.ApiSpec`, run `mix
openapi.export`, and commit `priv/openapi.json`.
## Test plan
- [x] `mix test`: 580 tests, 0 failures, 12 of them new contract tests.
- [x] `mix credo` reports no issues. `mix compile --warnings-as-errors`
is clean.
- [x] The tests fail on an undocumented response field and on a field
with the wrong type.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6
---
_Generated by [Claude
Code](https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Darwin D. Wu <darwin67@users.noreply.github.com>
Summary
The first of two stacked PRs that bring the API and CLI up to the redesigned paste model. This one is server-side only. The next PR adds the client and CLI on top of it.
url, the browser link people share, andraw_url, the exact stored bytes. Both are absolute. This covers the "API create/show responses contain canonical and raw URLs" item in RFD 3./api/v1/workspaces/:workspace_id:GET /api/v1/pastes/deletedlists restorable deleted and expired pastes. Each entry includesremoval_reason,removed_atandpurge_after, but never the content.POST /api/v1/pastes/deleted/:id/restorerestores a paste and returns its metadata.DELETE /api/v1/pastes/deleted/:iddeletes a paste permanently, before its recovery window ends.deletedroutes come before the/:idroutes, so "deleted" is never read as a paste id.Test plan
mix precommit: 563 tests, 0 failuresmix credo: no issues🤖 Generated with Claude Code
https://claude.ai/code/session_019fnjMN7WpEjQJD2zveakV6
Generated by Claude Code