mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Add bookmarks
This commit is contained in:
Vendored
+8
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -13,6 +13,7 @@ info:
|
||||
| Resource | What it is |
|
||||
|---|---|
|
||||
| **Page** | A bookmarked URL. When created, the server asynchronously fetches its title, description, and a screenshot. |
|
||||
| **Bookmark** | An archived social-media post (e.g. an x.com / Twitter tweet) saved with full preview data — text, author, images, video poster thumbnails, link cards, quotes — so the archive page renders offline. Bulk-import via `/api/bookmarks/import`. |
|
||||
| **Post** | A Markdown blog post, optionally tagged. |
|
||||
| **Collection** | A named group of uploaded images (e.g. a photo album). |
|
||||
| **Image** | An image file belonging to a Collection. |
|
||||
@@ -41,6 +42,20 @@ info:
|
||||
no authentication is required for the recipient. Set an expiry with
|
||||
`POST /api/files/{id}/set-expiry` to time-limit the link.
|
||||
|
||||
### Bookmarks bulk import
|
||||
Import (or upsert) many archived posts in one request. Each item accepts the
|
||||
full normalised export object from `x-bookmarks-exporter` (with nested
|
||||
`author`, `media`, `card`, `quoted`, `retweet`, counters); matching is by
|
||||
`tweet_id`:
|
||||
```
|
||||
POST /api/bookmarks/import
|
||||
{ "bookmarks": [ { "id": "123", "author": {"screen_name":"u"}, "text": "...",
|
||||
"media": [...], ... }, ... ] }
|
||||
→ { "created": 10, "updated": 0, "skipped": 0, "total": 769 }
|
||||
```
|
||||
Use `PUT /api/bookmarks/bulk` with a bare JSON array of flat
|
||||
`Bookmark`-shaped objects for the same upsert behaviour.
|
||||
|
||||
## Authentication
|
||||
No authentication is currently required on any endpoint.
|
||||
version: 1.0.0
|
||||
@@ -60,6 +75,12 @@ tags:
|
||||
the page title, description, and screenshot automatically.
|
||||
- name: Posts
|
||||
description: Markdown blog posts with optional tag categorisation.
|
||||
- name: Bookmarks
|
||||
description: |
|
||||
Archived social-media posts (originally x.com / Twitter bookmarks), each saved with
|
||||
full preview data — text, author, media poster thumbnails (no embedded video streams),
|
||||
link cards, quotes — so the archive viewer page renders offline without further API calls.
|
||||
Use `POST /api/bookmarks/import` for bulk upsert by `tweet_id`.
|
||||
- name: Collections
|
||||
description: Named groups of images. Each collection can hold many uploaded image files.
|
||||
- name: Images
|
||||
@@ -176,6 +197,266 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Page'
|
||||
|
||||
/api/bookmarks:
|
||||
get:
|
||||
operationId: listBookmarks
|
||||
tags: [Bookmarks]
|
||||
summary: List archived bookmarks
|
||||
description: |
|
||||
Paginated list of archived social-media bookmarks, newest first. Supports
|
||||
full-text search (`q`), boolean media/type filters, author filter and
|
||||
ordering. Default page size is 24; use `?page_size=` up to 200.
|
||||
parameters:
|
||||
- in: query
|
||||
name: page
|
||||
schema: { type: integer }
|
||||
description: 1-based page number
|
||||
- in: query
|
||||
name: page_size
|
||||
schema: { type: integer, maximum: 200 }
|
||||
description: Items per page (default 24)
|
||||
- in: query
|
||||
name: q
|
||||
schema: { type: string }
|
||||
description: Case-insensitive search across `text`, `summary`, `author_screen_name`, `author_name`
|
||||
- in: query
|
||||
name: has_video
|
||||
schema: { type: boolean }
|
||||
description: Filter to bookmarks containing a video/animated GIF
|
||||
- in: query
|
||||
name: has_photo
|
||||
schema: { type: boolean }
|
||||
- in: query
|
||||
name: has_card
|
||||
schema: { type: boolean }
|
||||
description: Has an attached link preview card
|
||||
- in: query
|
||||
name: is_quote
|
||||
schema: { type: boolean }
|
||||
- in: query
|
||||
name: is_retweet
|
||||
schema: { type: boolean }
|
||||
- in: query
|
||||
name: author
|
||||
schema: { type: string }
|
||||
description: Exact (case-insensitive) screen name
|
||||
- in: query
|
||||
name: ordering
|
||||
schema:
|
||||
type: string
|
||||
enum: [tweet_created_at, -tweet_created_at, bookmark_created_at,
|
||||
-bookmark_created_at, favorite_count, -favorite_count,
|
||||
view_count, -view_count, created_at, -created_at]
|
||||
description: Sort field (prefix `-` for descending). Default `-tweet_created_at`
|
||||
responses:
|
||||
'200':
|
||||
description: Paginated bookmarks
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BookmarkList'
|
||||
post:
|
||||
operationId: createBookmark
|
||||
tags: [Bookmarks]
|
||||
summary: Create a single bookmark
|
||||
description: |
|
||||
Create one archived bookmark from a flat `Bookmark`-shaped object.
|
||||
For importing many at once (e.g. from x-bookmarks-exporter), prefer
|
||||
`POST /api/bookmarks/import`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BookmarkCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: Bookmark created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Bookmark'
|
||||
|
||||
/api/bookmarks/stats:
|
||||
get:
|
||||
operationId: bookmarkStats
|
||||
tags: [Bookmarks]
|
||||
summary: Aggregate bookmark counts
|
||||
description: Returns total count plus per-flag counts (used by the viewer's filter chips).
|
||||
responses:
|
||||
'200':
|
||||
description: Aggregate stats
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
total: { type: integer }
|
||||
has_video: { type: integer }
|
||||
has_photo: { type: integer }
|
||||
has_card: { type: integer }
|
||||
is_quote: { type: integer }
|
||||
is_retweet: { type: integer }
|
||||
|
||||
/api/bookmarks/import:
|
||||
post:
|
||||
operationId: importBookmarks
|
||||
tags: [Bookmarks]
|
||||
summary: Bulk upsert bookmarks from export objects
|
||||
description: |
|
||||
Upserts many bookmarks in one request. Each item in `bookmarks` accepts the
|
||||
full normalised export object from `x-bookmarks-exporter` — with nested
|
||||
`author`, `media`, `card`, `quoted`, `retweet`, engagement counters and
|
||||
`created_at` (x.com-style RFC date string). Matching is by `tweet_id`
|
||||
(top-level `id` alias also accepted); existing entries are updated,
|
||||
new ones created. Returns `{ created, updated, skipped, total }`.
|
||||
|
||||
Example body:
|
||||
```json
|
||||
{
|
||||
"bookmarks": [
|
||||
{
|
||||
"id": "1234567890",
|
||||
"url": "https://x.com/user/status/1234567890",
|
||||
"author": {"screen_name":"user","name":"User","profile_image_url":"..."},
|
||||
"text": "Hello world",
|
||||
"media": [{"type":"video","media_url":"...","video":{"poster":"...","best_mp4":"...","duration_ms":12000}}],
|
||||
"favorite_count": 5, "view_count": 99
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [bookmarks]
|
||||
properties:
|
||||
bookmarks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BookmarkExportItem'
|
||||
responses:
|
||||
'200':
|
||||
description: Upsert summary
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
created: { type: integer }
|
||||
updated: { type: integer }
|
||||
skipped: { type: integer }
|
||||
total: { type: integer, description: Total bookmarks now in DB }
|
||||
|
||||
/api/bookmarks/bulk:
|
||||
put:
|
||||
operationId: bulkUpsertBookmarks
|
||||
tags: [Bookmarks]
|
||||
summary: Bulk upsert from a flat JSON array
|
||||
description: |
|
||||
Like `/import` but accepts a bare JSON array (not wrapped in `{bookmarks:[]}`).
|
||||
Each item can be a flat `Bookmark`-shaped object (matching
|
||||
`BookmarkCreate`/`Bookmark`) or a normalised export object; `tweet_id`
|
||||
(or top-level `id`) is used as the upsert key.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BookmarkCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: Upsert summary
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
created: { type: integer }
|
||||
updated: { type: integer }
|
||||
skipped: { type: integer }
|
||||
total: { type: integer }
|
||||
post:
|
||||
operationId: bulkUpsertBookmarksPost
|
||||
tags: [Bookmarks]
|
||||
summary: Bulk upsert from a flat JSON array (POST alias)
|
||||
description: Alias of `PUT /api/bookmarks/bulk` for clients that cannot send PUT.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BookmarkCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: Upsert summary
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
created: { type: integer }
|
||||
updated: { type: integer }
|
||||
skipped: { type: integer }
|
||||
total: { type: integer }
|
||||
|
||||
/api/bookmarks/{id}:
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
description: Internal DB primary key (NOT the tweet_id)
|
||||
get:
|
||||
operationId: retrieveBookmark
|
||||
tags: [Bookmarks]
|
||||
summary: Retrieve a single bookmark
|
||||
responses:
|
||||
'200':
|
||||
description: Bookmark detail
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Bookmark'
|
||||
'404':
|
||||
description: Not found
|
||||
patch:
|
||||
operationId: updateBookmark
|
||||
tags: [Bookmarks]
|
||||
summary: Partially update a bookmark
|
||||
description: |
|
||||
Update any subset of fields. Common use by agents: write an AI-generated
|
||||
`summary` for the post via `{"summary": "..."}`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BookmarkCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: Updated bookmark
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Bookmark'
|
||||
delete:
|
||||
operationId: deleteBookmark
|
||||
tags: [Bookmarks]
|
||||
summary: Delete a bookmark
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
'404':
|
||||
description: Not found
|
||||
|
||||
/api/posts:
|
||||
get:
|
||||
operationId: listPosts
|
||||
@@ -1182,3 +1463,147 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Link'
|
||||
|
||||
Bookmark:
|
||||
type: object
|
||||
description: |
|
||||
An archived social-media post. The `data` field holds the complete original
|
||||
normalised export object (kept for round-tripping / the offline viewer page);
|
||||
the top-level denormalised fields (text, media, card_data, quoted_data,
|
||||
flags, counters) are the indexed/fast-path versions used by list & filter.
|
||||
properties:
|
||||
id: { type: integer, description: Internal DB primary key }
|
||||
tweet_id: { type: string, description: Source post id (unique) }
|
||||
url: { type: string, format: uri, description: Canonical x.com post URL }
|
||||
author_screen_name: { type: string }
|
||||
author_name: { type: string }
|
||||
author_profile_image_url: { type: string, format: uri }
|
||||
text: { type: string, description: Full post text }
|
||||
summary: { type: string, description: Optional human/agent-written summary }
|
||||
tweet_created_at: { type: string, format: date-time, nullable: true }
|
||||
bookmark_created_at: { type: string, format: date-time, nullable: true }
|
||||
view_count: { type: integer }
|
||||
favorite_count: { type: integer }
|
||||
retweet_count: { type: integer }
|
||||
reply_count: { type: integer }
|
||||
bookmark_count: { type: integer }
|
||||
quote_count: { type: integer }
|
||||
has_video: { type: boolean }
|
||||
has_photo: { type: boolean }
|
||||
has_card: { type: boolean }
|
||||
is_quote: { type: boolean }
|
||||
is_retweet: { type: boolean }
|
||||
media:
|
||||
type: array
|
||||
description: List of media objects (photo or video/animated_gif with poster)
|
||||
items: { type: object }
|
||||
card_data:
|
||||
type: object
|
||||
nullable: true
|
||||
description: Link preview card (title, description, site, image, url)
|
||||
quoted_data:
|
||||
type: object
|
||||
nullable: true
|
||||
description: Nested quoted post (same shape as an export item)
|
||||
data:
|
||||
type: object
|
||||
description: The complete normalised export object from x-bookmarks-exporter
|
||||
created_at: { type: string, format: date-time }
|
||||
updated_at: { type: string, format: date-time }
|
||||
|
||||
BookmarkCreate:
|
||||
type: object
|
||||
required: [tweet_id]
|
||||
properties:
|
||||
tweet_id: { type: string }
|
||||
url: { type: string, format: uri }
|
||||
author_screen_name: { type: string }
|
||||
author_name: { type: string }
|
||||
author_profile_image_url: { type: string, format: uri }
|
||||
text: { type: string }
|
||||
summary: { type: string }
|
||||
tweet_created_at: { type: string, format: date-time, nullable: true }
|
||||
bookmark_created_at: { type: string, format: date-time, nullable: true }
|
||||
view_count: { type: integer }
|
||||
favorite_count: { type: integer }
|
||||
retweet_count: { type: integer }
|
||||
reply_count: { type: integer }
|
||||
bookmark_count: { type: integer }
|
||||
quote_count: { type: integer }
|
||||
has_video: { type: boolean }
|
||||
has_photo: { type: boolean }
|
||||
has_card: { type: boolean }
|
||||
is_quote: { type: boolean }
|
||||
is_retweet: { type: boolean }
|
||||
media:
|
||||
type: array
|
||||
items: { type: object }
|
||||
card_data:
|
||||
type: object
|
||||
nullable: true
|
||||
quoted_data:
|
||||
type: object
|
||||
nullable: true
|
||||
data:
|
||||
type: object
|
||||
|
||||
BookmarkExportItem:
|
||||
type: object
|
||||
description: |
|
||||
One element of a bulk-import payload, as produced by x-bookmarks-exporter.
|
||||
All nested fields are optional except the source post id; the server maps
|
||||
the nested `author`, `media`, `card`, `quoted`, `retweet`, counters and
|
||||
`created_at` date string into the flat Bookmark columns and the `data`
|
||||
JSON blob.
|
||||
properties:
|
||||
id: { type: string, description: Source tweet id (also accepted as `tweet_id`) }
|
||||
url: { type: string, format: uri }
|
||||
author:
|
||||
type: object
|
||||
properties:
|
||||
screen_name: { type: string }
|
||||
name: { type: string }
|
||||
profile_image_url: { type: string, format: uri }
|
||||
verified: { type: boolean }
|
||||
description: { type: string }
|
||||
text: { type: string }
|
||||
summary: { type: string }
|
||||
created_at: { type: string, description: x.com-style "Wed Jul 30 12:00:00 +0000 2025" }
|
||||
bookmark_created_at: { type: string }
|
||||
view_count: { type: integer }
|
||||
favorite_count: { type: integer }
|
||||
retweet_count: { type: integer }
|
||||
reply_count: { type: integer }
|
||||
bookmark_count: { type: integer }
|
||||
quote_count: { type: integer }
|
||||
media:
|
||||
type: array
|
||||
description: |
|
||||
Each media object carries `type` (photo|video|animated_gif), image
|
||||
URL variants (`media_url`, `media_thumb`, `media_large`, `media_orig`)
|
||||
and, for video/gif, a `video` object with `poster`, `best_mp4`,
|
||||
`duration_ms`, and `variants[]`.
|
||||
items: { type: object }
|
||||
card:
|
||||
type: object
|
||||
nullable: true
|
||||
description: Link preview with title/description/site/image/url
|
||||
quoted:
|
||||
type: object
|
||||
nullable: true
|
||||
description: Nested quoted post (recursive BookmarkExportItem shape)
|
||||
retweet:
|
||||
type: object
|
||||
nullable: true
|
||||
description: Nested retweeted post
|
||||
|
||||
BookmarkList:
|
||||
type: object
|
||||
properties:
|
||||
count: { type: integer }
|
||||
next: { type: string, nullable: true }
|
||||
previous: { type: string, nullable: true }
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Bookmark'
|
||||
|
||||
Reference in New Issue
Block a user