@omelhorsite/sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -130
- package/dist/index.js +4800 -610
- package/dist/types/client.d.ts +60 -3
- package/dist/types/http.d.ts +444 -19
- package/dist/types/index.d.ts +5 -1
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/resources/account.d.ts +71 -5
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +1841 -0
- package/dist/types/resources/auth/index.d.ts +39 -0
- package/dist/types/resources/auth/passkeys.d.ts +652 -0
- package/dist/types/resources/auth/sessions.d.ts +844 -0
- package/dist/types/resources/chests.d.ts +54 -3
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +2775 -0
- package/dist/types/resources/dynamicQrs.d.ts +39 -3
- package/dist/types/resources/forms.d.ts +176 -35
- package/dist/types/resources/index.d.ts +19 -8
- package/dist/types/resources/ipLookup.d.ts +20 -4
- package/dist/types/resources/jobs.d.ts +69 -25
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +1433 -0
- package/dist/types/resources/linkTrees.d.ts +142 -30
- package/dist/types/resources/media.d.ts +351 -0
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +1160 -0
- package/dist/types/resources/music/artists.d.ts +1068 -0
- package/dist/types/resources/music/imports.d.ts +941 -0
- package/dist/types/resources/music/index.d.ts +61 -0
- package/dist/types/resources/music/playlists.d.ts +1027 -0
- package/dist/types/resources/music/social.d.ts +1132 -0
- package/dist/types/resources/music/songs.d.ts +1167 -0
- package/dist/types/resources/notepads.d.ts +4 -1
- package/dist/types/resources/quotas.d.ts +7 -1
- package/dist/types/resources/realtime.d.ts +855 -0
- package/dist/types/resources/shortLinks.d.ts +49 -5
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +1348 -0
- package/dist/types/resources/storage/upload.d.ts +158 -11
- package/dist/types/resources/storage.d.ts +111 -27
- package/dist/types/resources/tickets.d.ts +101 -13
- package/dist/types/resources/tools/backgroundRemoval.d.ts +18 -3
- package/dist/types/resources/tools/captions.d.ts +448 -21
- package/dist/types/resources/tools/downloader.d.ts +21 -0
- package/dist/types/resources/tools/index.d.ts +57 -15
- package/dist/types/resources/tools/jumpstyle.d.ts +50 -17
- package/dist/types/resources/tools/transcription.d.ts +35 -13
- package/dist/types/resources/tools/upscale.d.ts +23 -3
- package/dist/types/resources/tools/vocalSeparation.d.ts +30 -13
- package/dist/types/types.d.ts +249 -17
- package/package.json +2 -1
|
@@ -141,11 +141,62 @@ export declare class ChestEntriesNamespace extends Resource {
|
|
|
141
141
|
/**
|
|
142
142
|
* `GET /chest_entries/:id/data` - the entry's bytes.
|
|
143
143
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
144
|
+
* SENT WITH NO CREDENTIAL, deliberately. `ChestEntriesController` lists
|
|
145
|
+
* `data` in `allow_unauthenticated_access`, and the action itself is three
|
|
146
|
+
* lines: find the entry by id, check that something is attached, redirect.
|
|
147
|
+
* It never looks at the chest name, the chest token, or who is asking. The
|
|
148
|
+
* ENTRY ID IS THE WHOLE CAPABILITY - which is worth knowing for its own sake,
|
|
149
|
+
* and which also means a credential on this request could not possibly change
|
|
150
|
+
* the answer.
|
|
151
|
+
*
|
|
152
|
+
* That matters because sending one breaks the call in a browser. The action
|
|
153
|
+
* answers `302` to `minio.omelhorsite.pt`, and following that hop replaces the
|
|
154
|
+
* request's origin with an opaque one (Fetch standard: a cross-origin
|
|
155
|
+
* redirect of a CORS request whose origin already differs from the current
|
|
156
|
+
* URL's origin), so the store sees `Origin: null` and answers
|
|
157
|
+
* `Access-Control-Allow-Origin: *`. Wildcard plus credentials is illegal, so a
|
|
158
|
+
* client built with `sessionCookie: true` - the production web app - would
|
|
159
|
+
* have the browser reject the bytes with an opaque "Failed to fetch". Asking
|
|
160
|
+
* anonymously sidesteps it: `*` is fine for an uncredentialed request.
|
|
161
|
+
*
|
|
162
|
+
* Two shapes come back and both are handled here. Against MinIO it is the
|
|
163
|
+
* `302`. Against a Disk service (dev, test) presigning raises `ArgumentError`
|
|
164
|
+
* and the controller falls back to `send_data`, so the bytes arrive inline
|
|
165
|
+
* from Rails with a `Content-Disposition`. Either way this returns the bytes.
|
|
166
|
+
*
|
|
167
|
+
* Going around the transport costs the usual thing: no retry, no per-call
|
|
168
|
+
* deadline, only the caller's `signal`. Use {@link downloadUrl} when the
|
|
169
|
+
* destination is an `<a download>` or a media element rather than memory.
|
|
170
|
+
*
|
|
171
|
+
* @throws {OmsApiError} 404 when the entry is unknown, its bytes never
|
|
172
|
+
* landed, or the chest has passed its two-hour expiry and been swept.
|
|
147
173
|
*/
|
|
148
174
|
download(id: Id, options?: RequestOptions): Promise<Blob>;
|
|
175
|
+
/**
|
|
176
|
+
* Absolute URL for an entry's bytes, for an `<a download>`, a `<video>`, or a
|
|
177
|
+
* new tab.
|
|
178
|
+
*
|
|
179
|
+
* Synchronous and credential-free, because the endpoint is: the entry id is
|
|
180
|
+
* the only thing it checks. The frontend's older helper appended a `?token=`
|
|
181
|
+
* here; this deliberately does not, because a session token in a URL that
|
|
182
|
+
* ends up in markup, in a shared link and in an access log buys precisely
|
|
183
|
+
* nothing on a route that never reads it.
|
|
184
|
+
*
|
|
185
|
+
* Treat the URL as a bearer capability all the same. Anyone holding it can
|
|
186
|
+
* pull the file until the chest expires, so it is exactly as shareable as the
|
|
187
|
+
* chest name and no more.
|
|
188
|
+
*
|
|
189
|
+
* ```tsx
|
|
190
|
+
* <a href={oms.chests.entries.downloadUrl(entry.id)} download={entry.name}>
|
|
191
|
+
* {entry.name}
|
|
192
|
+
* </a>
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* A link is also the better answer for a large entry: the browser streams it
|
|
196
|
+
* straight to disk, where {@link download} would buffer the whole file in
|
|
197
|
+
* memory first.
|
|
198
|
+
*/
|
|
199
|
+
downloadUrl(id: Id): string;
|
|
149
200
|
/**
|
|
150
201
|
* `DELETE /chest_entries/:id` - removes an entry and gives its bytes back to
|
|
151
202
|
* the chest's ceiling.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Admin storage reports. */
|
|
2
|
+
import { Resource } from "../../http";
|
|
3
|
+
import type { RequestOptions } from "../../types";
|
|
4
|
+
import type { FsNode } from "../storage";
|
|
5
|
+
/** One day in a daily-count series. */
|
|
6
|
+
export interface AnalysisDailyPoint {
|
|
7
|
+
/** `YYYY-MM-DD`, server timezone. */
|
|
8
|
+
readonly date: string;
|
|
9
|
+
/** Rows created that day. `0` for a day with none - the series has no gaps. */
|
|
10
|
+
readonly count: number;
|
|
11
|
+
}
|
|
12
|
+
/** Days covered by {@link AnalysisNamespace.filesDaily}. Mirrors `DAILY_WINDOW_DAYS`. */
|
|
13
|
+
export declare const ANALYSIS_DAILY_WINDOW_DAYS = 30;
|
|
14
|
+
/**
|
|
15
|
+
* The `analysis` namespace: two admin reports about storage.
|
|
16
|
+
*
|
|
17
|
+
* **Admin only, and the refusal is unusual.** `require_admin!` answers `403`
|
|
18
|
+
* whose body is a long quotation from Monster House rather than an error code,
|
|
19
|
+
* so do not try to match on the message - check the status. An anonymous
|
|
20
|
+
* caller is stopped earlier, by the authentication filter, with the ordinary
|
|
21
|
+
* `401 "Session required to access this resource."`.
|
|
22
|
+
*
|
|
23
|
+
* Paths like `GET /analysis` and `GET /analysis/:id` exist in the router with
|
|
24
|
+
* no action behind them: calling one is a `404` carrying an HTML error page
|
|
25
|
+
* rather than this API's usual bare string. There are exactly two usable
|
|
26
|
+
* routes here and they are both below.
|
|
27
|
+
*/
|
|
28
|
+
export declare class AnalysisNamespace extends Resource {
|
|
29
|
+
/**
|
|
30
|
+
* `GET /analysis/storages` - every root directory in the system, with its
|
|
31
|
+
* recursive size.
|
|
32
|
+
*
|
|
33
|
+
* Every node with no parent, for every user, which in practice means each
|
|
34
|
+
* account's home, trash and vault roots. Rendered in the DEFAULT view, which
|
|
35
|
+
* is what {@link FsNode} describes.
|
|
36
|
+
*
|
|
37
|
+
* Two things to expect:
|
|
38
|
+
*
|
|
39
|
+
* - **No owner.** The payload does not carry `user_id`, so it tells you
|
|
40
|
+
* that a root called `"home"` holds 40 GB and not whose it is.
|
|
41
|
+
* Correlating means another query.
|
|
42
|
+
* - **No limit and no paging.** The scope is unbounded, so the response
|
|
43
|
+
* grows linearly with the number of accounts. It is an admin report, not
|
|
44
|
+
* something to poll.
|
|
45
|
+
*
|
|
46
|
+
* The `size` on a root is the recursive total maintained by the storage
|
|
47
|
+
* layer; it has drifted from the true sum before, so read it as an estimate.
|
|
48
|
+
*/
|
|
49
|
+
storages(options?: RequestOptions): Promise<FsNode[]>;
|
|
50
|
+
/**
|
|
51
|
+
* `GET /analysis/files_daily` - files created per day over the last
|
|
52
|
+
* {@link ANALYSIS_DAILY_WINDOW_DAYS} days. Unwraps `{"creations_daily": [...]}`.
|
|
53
|
+
*
|
|
54
|
+
* Exactly 30 entries, oldest first, zero-filled: a day with no uploads is
|
|
55
|
+
* present with `count: 0`. The last entry is today and is partial.
|
|
56
|
+
*
|
|
57
|
+
* Counts `fs_nodes` of kind `file` by `DATE(created_at)`, so it measures
|
|
58
|
+
* node creation and not bytes - a folder copy that mints 50 000 nodes shows
|
|
59
|
+
* up here as 50 000 files.
|
|
60
|
+
*/
|
|
61
|
+
filesDaily(options?: RequestOptions): Promise<AnalysisDailyPoint[]>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/** Blogs, blog posts and subscriptions. */
|
|
2
|
+
import { type ApiClient, Resource } from "../../http";
|
|
3
|
+
import type { Id, RequestOptions, Timestamp } from "../../types";
|
|
4
|
+
/** Primary key of a blog. An INTEGER: `blogs` kept its auto-increment id. */
|
|
5
|
+
export type BlogId = number;
|
|
6
|
+
/** Primary key of a blog post. An integer, like the blog it hangs off. */
|
|
7
|
+
export type BlogPostId = number;
|
|
8
|
+
/** The author of a blog, embedded in {@link Blog}. Not a full user record. */
|
|
9
|
+
export interface BlogAuthor {
|
|
10
|
+
/** User id. A STRING, unlike every other id in this section. */
|
|
11
|
+
readonly id: Id;
|
|
12
|
+
readonly handle: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A blog: one per user, created lazily.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately NOT a `BaseRecord`: the payload has `created_at` and NO
|
|
19
|
+
* `updated_at`. Do not reach for one.
|
|
20
|
+
*/
|
|
21
|
+
export interface Blog {
|
|
22
|
+
readonly id: BlogId;
|
|
23
|
+
/**
|
|
24
|
+
* URL-safe handle of the blog, and the ONLY way to address it on the read
|
|
25
|
+
* routes. Matches `/\A[a-z0-9][a-z0-9_-]*\z/`, 1-64 characters, unique
|
|
26
|
+
* across the whole table. Defaults to the owner's handle, lowercased.
|
|
27
|
+
*/
|
|
28
|
+
readonly slug: string;
|
|
29
|
+
/** Display name. Defaults to `"<name>'s blog"`. Up to 80 characters. */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/** Up to 240 characters, or `null`. */
|
|
32
|
+
readonly description: string | null;
|
|
33
|
+
/** Who owns it. One blog per user, enforced by a unique index on `user_id`. */
|
|
34
|
+
readonly user: BlogAuthor;
|
|
35
|
+
/**
|
|
36
|
+
* Subscribers with a `confirmed_at`, counted live on every render.
|
|
37
|
+
*
|
|
38
|
+
* In practice that is every subscriber: the only reachable way to subscribe
|
|
39
|
+
* requires a session, and a signed-in subscription is confirmed on the spot.
|
|
40
|
+
* See {@link BlogsNamespace.subscribe}.
|
|
41
|
+
*/
|
|
42
|
+
readonly followers_count: number;
|
|
43
|
+
/** Posts with a `published_at`, counted live on every render. */
|
|
44
|
+
readonly published_posts_count: number;
|
|
45
|
+
readonly created_at: Timestamp;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the CALLING user subscribes to this blog. Computed against the
|
|
48
|
+
* caller, so the same row differs per identity - never cache it across
|
|
49
|
+
* identities, and note it is `false` (not `null`) for an anonymous caller.
|
|
50
|
+
*/
|
|
51
|
+
readonly is_following: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A post as it appears in a listing: the summary view.
|
|
55
|
+
*
|
|
56
|
+
* Like {@link Blog}, this is not a `BaseRecord`: the summary carries neither
|
|
57
|
+
* `created_at` nor `updated_at`. They appear only on {@link BlogPost}, the
|
|
58
|
+
* `:extended` view, which is this shape PLUS extras, never a subset.
|
|
59
|
+
*/
|
|
60
|
+
export interface BlogPostSummary {
|
|
61
|
+
readonly id: BlogPostId;
|
|
62
|
+
/** URL-safe, unique within the blog. Derived from the title when omitted. */
|
|
63
|
+
readonly slug: string;
|
|
64
|
+
/** Up to 200 characters. */
|
|
65
|
+
readonly title: string;
|
|
66
|
+
/**
|
|
67
|
+
* Up to 240 characters, derived from the first characters of `content_md`
|
|
68
|
+
* with the markdown punctuation stripped, unless the author wrote one. The
|
|
69
|
+
* derivation runs in a `before_save`, so it is refreshed on every write
|
|
70
|
+
* where the excerpt is blank - and never once it is not.
|
|
71
|
+
*/
|
|
72
|
+
readonly excerpt: string | null;
|
|
73
|
+
/** `null` for a draft. Presence of this field IS the published flag. */
|
|
74
|
+
readonly published_at: Timestamp | null;
|
|
75
|
+
/**
|
|
76
|
+
* Estimated reading time, recomputed on every save at 220 words per minute
|
|
77
|
+
* and floored at 1. Server-owned: sending it is ignored.
|
|
78
|
+
*/
|
|
79
|
+
readonly reading_minutes: number;
|
|
80
|
+
/** Lowercased, de-duplicated, at most 10. Never `null` in the payload. */
|
|
81
|
+
readonly tags: string[];
|
|
82
|
+
/** The blog it belongs to, trimmed to three fields. */
|
|
83
|
+
readonly blog: {
|
|
84
|
+
readonly id: BlogId;
|
|
85
|
+
readonly slug: string;
|
|
86
|
+
readonly name: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** A post with its body: the `:extended` view, returned by every single-post route. */
|
|
90
|
+
export interface BlogPost extends BlogPostSummary {
|
|
91
|
+
/**
|
|
92
|
+
* The markdown source, up to 200 000 characters.
|
|
93
|
+
*
|
|
94
|
+
* There is a `content_html` column next to it in the database, rendered on
|
|
95
|
+
* write - but no response exposes it, so the client renders the markdown
|
|
96
|
+
* itself.
|
|
97
|
+
*/
|
|
98
|
+
readonly content_md: string | null;
|
|
99
|
+
readonly created_at: Timestamp;
|
|
100
|
+
readonly updated_at: Timestamp;
|
|
101
|
+
/** Whether the CALLING user may edit it. Per-viewer, like `is_following`. */
|
|
102
|
+
readonly is_owner: boolean;
|
|
103
|
+
}
|
|
104
|
+
/** A {@link Blog} plus the posts the caller may see. Returned by `show` and `mine`. */
|
|
105
|
+
export interface BlogWithPosts extends Blog {
|
|
106
|
+
/**
|
|
107
|
+
* Newest first (`published_at DESC, created_at DESC`).
|
|
108
|
+
*
|
|
109
|
+
* NOT paginated and NOT capped: `blog.blog_posts.viewable_by(...).recent`
|
|
110
|
+
* runs with no `limit`, so a blog with a thousand posts returns a thousand
|
|
111
|
+
* summaries in one response. This is the one listing in the file with no
|
|
112
|
+
* ceiling of any kind.
|
|
113
|
+
*
|
|
114
|
+
* On `show` this is published posts only, unless the caller owns the blog,
|
|
115
|
+
* in which case drafts are included too. On `mine` it is every post,
|
|
116
|
+
* published or not.
|
|
117
|
+
*/
|
|
118
|
+
readonly posts: BlogPostSummary[];
|
|
119
|
+
}
|
|
120
|
+
/** Fields {@link BlogsNamespace.updateMine} may change. */
|
|
121
|
+
export interface UpdateBlogInput {
|
|
122
|
+
/**
|
|
123
|
+
* New slug. Must match `/\A[a-z0-9][a-z0-9_-]*\z/` (1-64 chars) and be free
|
|
124
|
+
* across the whole table, or the call is `400`. Changing it BREAKS every
|
|
125
|
+
* link already published against the old one: there is no redirect and no
|
|
126
|
+
* history table.
|
|
127
|
+
*/
|
|
128
|
+
readonly slug?: string;
|
|
129
|
+
/** Up to 80 characters. */
|
|
130
|
+
readonly name?: string;
|
|
131
|
+
/** Up to 240 characters. */
|
|
132
|
+
readonly description?: string | null;
|
|
133
|
+
}
|
|
134
|
+
/** Result of a subscribe call. */
|
|
135
|
+
export interface BlogSubscribeResult {
|
|
136
|
+
readonly ok: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* `true` when the subscription is live.
|
|
139
|
+
*
|
|
140
|
+
* In practice it is ALWAYS `true` today. The field exists because the
|
|
141
|
+
* controller can also create an unconfirmed, email-only subscription - but
|
|
142
|
+
* that branch is unreachable over HTTP (see
|
|
143
|
+
* {@link BlogsNamespace.subscribe}), so every row this route can actually
|
|
144
|
+
* create is a signed-in one, and `set_confirmed_at_for_user_subs` confirms
|
|
145
|
+
* those on insert.
|
|
146
|
+
*
|
|
147
|
+
* Were a `false` ever to reach you, there would be nothing to do about it:
|
|
148
|
+
* the row carries an `unsubscribe_token`, but no route in the application
|
|
149
|
+
* reads it, so there is no confirmation step to complete.
|
|
150
|
+
*/
|
|
151
|
+
readonly confirmed: boolean;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The `blogs` namespace: one blog per user, markdown posts, and a subscriber
|
|
155
|
+
* list.
|
|
156
|
+
*
|
|
157
|
+
* The read routes address a blog by its SLUG, never by its id - the lookup is
|
|
158
|
+
* by lowercased slug and nothing else, so passing the numeric id gets
|
|
159
|
+
* `404 "Blog not found"`. The two subscribe routes are the exception: they try
|
|
160
|
+
* the slug first and then fall back to the id, so they accept either.
|
|
161
|
+
*
|
|
162
|
+
* Reads (`GET /blogs`, `GET /blogs/:slug`, `GET /blog_posts`,
|
|
163
|
+
* `GET /blog_posts/:id`, `GET /blogs/:blog/posts/:slug`) are open to anonymous
|
|
164
|
+
* callers; everything else needs a session.
|
|
165
|
+
*/
|
|
166
|
+
export declare class BlogsNamespace extends Resource {
|
|
167
|
+
/** Posts, blog metadata and publishing. Also mounted as `oms.blogPosts`. */
|
|
168
|
+
readonly posts: BlogPostsNamespace;
|
|
169
|
+
constructor(http: ApiClient);
|
|
170
|
+
/**
|
|
171
|
+
* `GET /blogs` - the discovery feed: the 30 most recent PUBLISHED posts
|
|
172
|
+
* across every blog on the site.
|
|
173
|
+
*
|
|
174
|
+
* Despite the path this returns POSTS, not blogs, and there is no endpoint
|
|
175
|
+
* anywhere that lists blogs. The wire shape is `{"posts": [...]}`, one of
|
|
176
|
+
* the two envelopes in this file; the array is unwrapped here.
|
|
177
|
+
*
|
|
178
|
+
* Fixed at 30 rows, newest first. No paging, no filters, no `search` - the
|
|
179
|
+
* action is `BlogPost.published.recent.limit(30)` and reads nothing off the
|
|
180
|
+
* query string, so anything you add to it is ignored rather than rejected.
|
|
181
|
+
* To go deeper than 30, there is nothing to page: this is a front page, not
|
|
182
|
+
* an archive.
|
|
183
|
+
*
|
|
184
|
+
* Anonymous-safe, and drafts never leak into it regardless of who asks.
|
|
185
|
+
*/
|
|
186
|
+
discover(options?: RequestOptions): Promise<BlogPostSummary[]>;
|
|
187
|
+
/**
|
|
188
|
+
* `GET /blogs/:slug` - one blog with all of its visible posts.
|
|
189
|
+
*
|
|
190
|
+
* The slug is lowercased by the server before the lookup, so case does not
|
|
191
|
+
* matter. A numeric id does NOT work here; use the slug.
|
|
192
|
+
*
|
|
193
|
+
* Published posts only, unless the caller OWNS the blog, in which case their
|
|
194
|
+
* drafts are included. `BlogPost.viewable_by` keys on `blog.user_id` alone,
|
|
195
|
+
* so an admin looking at somebody else's blog sees exactly what the public
|
|
196
|
+
* sees. The `posts` array is unbounded - see {@link BlogWithPosts.posts}.
|
|
197
|
+
*
|
|
198
|
+
* @throws {OmsApiError} 404 `"Blog not found"`.
|
|
199
|
+
*/
|
|
200
|
+
show(slug: string, options?: RequestOptions): Promise<BlogWithPosts>;
|
|
201
|
+
/**
|
|
202
|
+
* `GET /blogs/mine` - the caller's own blog, drafts included.
|
|
203
|
+
*
|
|
204
|
+
* **This read has a side effect.** `Blog.find_or_create_for` CREATES the
|
|
205
|
+
* blog row on first call, with the slug defaulted to the caller's handle and
|
|
206
|
+
* the name defaulted to `"<name>'s blog"`, and the creation fires a Discord
|
|
207
|
+
* `blog_created` alert. So "does this user have a blog" is not a question
|
|
208
|
+
* this endpoint can answer - by the time it replies, they do. Call it when
|
|
209
|
+
* the user opens their blog dashboard, not to probe.
|
|
210
|
+
*
|
|
211
|
+
* The default slug is the user's handle, which can collide with a blog
|
|
212
|
+
* somebody already owns under that slug - handles and blog slugs are
|
|
213
|
+
* separate namespaces and nothing keeps them apart. `create!` then raises
|
|
214
|
+
* `ActiveRecord::RecordInvalid`, which reaches the caller as a `422` with a
|
|
215
|
+
* Rails error page rather than this API's usual bare string, and fires a
|
|
216
|
+
* Discord error alert on the way out. Rare, and unfixable from the client:
|
|
217
|
+
* the endpoint takes no arguments.
|
|
218
|
+
*
|
|
219
|
+
* @throws {OmsApiError} 401 `"Session required to access this resource."`.
|
|
220
|
+
* The action also carries its own `unauthorized!("Not authenticated")`
|
|
221
|
+
* guard, but `allow_unauthenticated_access` covers only `index` and `show`,
|
|
222
|
+
* so the framework filter fires first and that message never ships.
|
|
223
|
+
*/
|
|
224
|
+
mine(options?: RequestOptions): Promise<BlogWithPosts>;
|
|
225
|
+
/**
|
|
226
|
+
* `PATCH /blogs/mine` - renames or re-slugs the caller's blog.
|
|
227
|
+
*
|
|
228
|
+
* Creates the blog first if there is none, exactly like {@link mine}, so
|
|
229
|
+
* this can be the very first call a client makes.
|
|
230
|
+
*
|
|
231
|
+
* Answers the blog ALONE - no `posts` key, unlike every other blog route.
|
|
232
|
+
* That asymmetry is the reason this returns {@link Blog} and not
|
|
233
|
+
* {@link BlogWithPosts}.
|
|
234
|
+
*
|
|
235
|
+
* Only `slug`, `name` and `description` are permitted; anything else in the
|
|
236
|
+
* body is dropped in silence. Re-slugging breaks published links - see
|
|
237
|
+
* {@link UpdateBlogInput.slug}.
|
|
238
|
+
*
|
|
239
|
+
* @throws {OmsApiError} 400 with the validation sentence when the slug is
|
|
240
|
+
* taken or malformed; 401 `"Session required to access this resource."`.
|
|
241
|
+
*/
|
|
242
|
+
updateMine(input: UpdateBlogInput, options?: RequestOptions): Promise<Blog>;
|
|
243
|
+
/**
|
|
244
|
+
* `POST /blogs/:slug/subscribe` - follows a blog.
|
|
245
|
+
*
|
|
246
|
+
* **Signed-in callers only, despite appearances.** The action reads like it
|
|
247
|
+
* supports anonymous email subscriptions - there is a whole branch for it,
|
|
248
|
+
* ending in `400 "Email required for anonymous subscribe"` - but
|
|
249
|
+
* `allow_unauthenticated_access` covers only `index` and `show`, so
|
|
250
|
+
* `require_authentication` rejects an anonymous caller with
|
|
251
|
+
* `401 "Session required to access this resource."` long before that branch
|
|
252
|
+
* runs. The email path is dead code today.
|
|
253
|
+
*
|
|
254
|
+
* Which means `email` is effectively ignored: with a session present the
|
|
255
|
+
* controller always takes the user branch, the subscription is attached to
|
|
256
|
+
* the account, and it is confirmed on insert, so `confirmed` is always
|
|
257
|
+
* `true` and `followers_count` moves. The parameter is kept here because the
|
|
258
|
+
* server accepts it and because the branch could be revived by one line in
|
|
259
|
+
* the controller - not because sending it changes anything today.
|
|
260
|
+
*
|
|
261
|
+
* Idempotent by construction: `find_or_initialize_by` on
|
|
262
|
+
* `(blog, user, email)` means subscribing twice is a no-op that answers
|
|
263
|
+
* `200` both times, so this is one of the few POSTs here where a retry
|
|
264
|
+
* cannot duplicate anything.
|
|
265
|
+
*
|
|
266
|
+
* Accepts either the slug or the numeric id in the path - this route and its
|
|
267
|
+
* `DELETE` twin are the only ones in the file that do.
|
|
268
|
+
*
|
|
269
|
+
* @throws {OmsApiError} 404 `"Blog not found"`; 401 without a session.
|
|
270
|
+
*/
|
|
271
|
+
subscribe(slugOrId: string | BlogId, input?: {
|
|
272
|
+
readonly email?: string;
|
|
273
|
+
}, options?: RequestOptions): Promise<BlogSubscribeResult>;
|
|
274
|
+
/**
|
|
275
|
+
* `DELETE /blogs/:slug/subscribe` - unfollows a blog.
|
|
276
|
+
*
|
|
277
|
+
* Signed-in callers only, and it removes only the CALLER's own subscription:
|
|
278
|
+
* the scope is `where(blog:, user: Current.user)`, so an email-only row
|
|
279
|
+
* (were one to exist) could not be removed through here at all.
|
|
280
|
+
*
|
|
281
|
+
* Answers `{"ok": true}` whether or not a subscription existed - it is a
|
|
282
|
+
* `delete_all` on a scope, so "not subscribed" and "unsubscribed" are the
|
|
283
|
+
* same answer, and a double call is harmless.
|
|
284
|
+
*
|
|
285
|
+
* @throws {OmsApiError} 404 `"Blog not found"`; 401
|
|
286
|
+
* `"Session required to access this resource."`.
|
|
287
|
+
*/
|
|
288
|
+
unsubscribe(slugOrId: string | BlogId, options?: RequestOptions): Promise<{
|
|
289
|
+
ok: boolean;
|
|
290
|
+
}>;
|
|
291
|
+
}
|
|
292
|
+
/** Arguments for {@link BlogPostsNamespace.create}. */
|
|
293
|
+
export interface CreateBlogPostInput {
|
|
294
|
+
/** Required, up to 200 characters. */
|
|
295
|
+
readonly title: string;
|
|
296
|
+
/**
|
|
297
|
+
* URL-safe slug, unique within the blog. Omit it and the server derives one
|
|
298
|
+
* from the title (lowercased, non-alphanumerics collapsed to `-`, trimmed to
|
|
299
|
+
* 80 characters), falling back to `post-<6 hex>` when the title has no
|
|
300
|
+
* alphanumerics at all - a title written entirely in a non-Latin script
|
|
301
|
+
* therefore gets a random slug, not a transliterated one.
|
|
302
|
+
*/
|
|
303
|
+
readonly slug?: string;
|
|
304
|
+
/** Markdown source, up to 200 000 characters. */
|
|
305
|
+
readonly content_md?: string;
|
|
306
|
+
/**
|
|
307
|
+
* Up to 280 characters by the model, but only 240 are ever written by the
|
|
308
|
+
* derivation. Leave it out and the server writes the first 240 characters of
|
|
309
|
+
* the stripped markdown; it is re-derived on every save where it is blank,
|
|
310
|
+
* so clearing it back to `""` re-enables the automatic one.
|
|
311
|
+
*/
|
|
312
|
+
readonly excerpt?: string;
|
|
313
|
+
/**
|
|
314
|
+
* Up to 10 tags. Lowercased, trimmed, de-duplicated and blank-filtered by
|
|
315
|
+
* the controller, and anything past the tenth is dropped in silence.
|
|
316
|
+
*/
|
|
317
|
+
readonly tags?: readonly string[];
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Arguments for {@link BlogPostsNamespace.update}.
|
|
321
|
+
*
|
|
322
|
+
* `tags` is optional in the type and DANGEROUS to omit. `post_params` ends
|
|
323
|
+
* with an unconditional `raw[:tags] = (raw[:tags] || []) ...`, so a PATCH that
|
|
324
|
+
* does not mention tags assigns the EMPTY ARRAY over whatever the post had.
|
|
325
|
+
* There is no partial-update semantics for this field: resend the tags you
|
|
326
|
+
* already hold on every update, or watch them disappear. Same shape of bug as
|
|
327
|
+
* `manifest_json` on movie addons.
|
|
328
|
+
*/
|
|
329
|
+
export interface UpdateBlogPostInput {
|
|
330
|
+
readonly title?: string;
|
|
331
|
+
/** Changing it breaks published links; uniqueness is scoped to the blog. */
|
|
332
|
+
readonly slug?: string;
|
|
333
|
+
readonly content_md?: string;
|
|
334
|
+
readonly excerpt?: string;
|
|
335
|
+
/** ALWAYS send this. Omitting it clears the post's tags. See the interface docs. */
|
|
336
|
+
readonly tags?: readonly string[];
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Posts, reachable as `oms.content.blogs.posts`.
|
|
340
|
+
*
|
|
341
|
+
* A post lives under exactly one blog and a user has exactly one blog, so
|
|
342
|
+
* there is no "which blog" argument anywhere: {@link create} always writes to
|
|
343
|
+
* the caller's own, creating it if needed.
|
|
344
|
+
*/
|
|
345
|
+
export declare class BlogPostsNamespace extends Resource {
|
|
346
|
+
/**
|
|
347
|
+
* `GET /blog_posts` - the 50 most recent posts the caller may see, newest
|
|
348
|
+
* first, optionally narrowed to one blog.
|
|
349
|
+
*
|
|
350
|
+
* Envelope: the wire shape is `{"posts": [...]}` and the array is unwrapped
|
|
351
|
+
* here.
|
|
352
|
+
*
|
|
353
|
+
* NOT the list DSL, despite the plural path. `blog_slug` is the only
|
|
354
|
+
* parameter the action reads; `search`, `exact_search` and `modifiers` are
|
|
355
|
+
* ignored rather than rejected, and the limit of 50 is not negotiable. There
|
|
356
|
+
* is no way to page past it, so this is a feed and not an archive - to walk
|
|
357
|
+
* a whole blog, read {@link BlogsNamespace.show}, whose `posts` array is
|
|
358
|
+
* uncapped.
|
|
359
|
+
*
|
|
360
|
+
* Visibility follows the caller: published posts always, plus the caller's
|
|
361
|
+
* OWN drafts. An anonymous caller sees published posts only.
|
|
362
|
+
*
|
|
363
|
+
* @param input.blogSlug Restrict to one blog. An unknown slug does NOT 404 -
|
|
364
|
+
* `find_by` returns nil and the action silently falls back to the
|
|
365
|
+
* site-wide listing, so a typo here returns everybody's posts instead of
|
|
366
|
+
* an empty list. Check the `blog` on each row if that distinction matters.
|
|
367
|
+
*/
|
|
368
|
+
list(input?: {
|
|
369
|
+
readonly blogSlug?: string;
|
|
370
|
+
}, options?: RequestOptions): Promise<BlogPostSummary[]>;
|
|
371
|
+
/**
|
|
372
|
+
* `GET /blog_posts/:id` - one post by numeric id, with its body.
|
|
373
|
+
*
|
|
374
|
+
* @throws {OmsApiError} 404 `"Post not found"`; **401**
|
|
375
|
+
* `"Draft only visible to author"` when the post exists but is
|
|
376
|
+
* unpublished and the caller is not its author. Note that this is a 401
|
|
377
|
+
* rather than a 404, so it confirms that a draft with that id exists.
|
|
378
|
+
*/
|
|
379
|
+
get(id: BlogPostId, options?: RequestOptions): Promise<BlogPost>;
|
|
380
|
+
/**
|
|
381
|
+
* `GET /blogs/:blogSlug/posts/:slug` - one post by the pair of slugs, which
|
|
382
|
+
* is the shape a public permalink has.
|
|
383
|
+
*
|
|
384
|
+
* **This route needs a session, and the id route does not.** That is almost
|
|
385
|
+
* certainly a mistake in the backend and it is worth knowing before you
|
|
386
|
+
* build a public permalink on it: an anonymous reader following a shared
|
|
387
|
+
* link gets `401 "Session required to access this resource."` here, while
|
|
388
|
+
* {@link get} hands them the very same published post. Until that is fixed,
|
|
389
|
+
* render public permalinks through {@link get} with the numeric id, or
|
|
390
|
+
* expect signed-in readers only.
|
|
391
|
+
*
|
|
392
|
+
* Both slugs are lowercased server-side before the lookup. The route is
|
|
393
|
+
* declared with `constraints: { blog_slug: /[^\/]+/, slug: /[^\/]+/ }`, so a
|
|
394
|
+
* slug containing a slash cannot reach it at all - not a concern for
|
|
395
|
+
* server-minted slugs, which are `[a-z0-9_-]` only.
|
|
396
|
+
*
|
|
397
|
+
* @throws {OmsApiError} 401 without a session, before anything else is
|
|
398
|
+
* checked; 404 `"Blog not found"` or `"Post not found"`; 401
|
|
399
|
+
* `"Draft only visible to author"`.
|
|
400
|
+
*/
|
|
401
|
+
getBySlugs(blogSlug: string, slug: string, options?: RequestOptions): Promise<BlogPost>;
|
|
402
|
+
/**
|
|
403
|
+
* `POST /blog_posts` - writes a new post to the caller's own blog. `201`.
|
|
404
|
+
*
|
|
405
|
+
* There is no blog argument because there is no choice: the controller calls
|
|
406
|
+
* `Blog.find_or_create_for(Current.user)`, so this CREATES the caller's blog
|
|
407
|
+
* as a side effect on their very first post, exactly like
|
|
408
|
+
* {@link BlogsNamespace.mine}.
|
|
409
|
+
*
|
|
410
|
+
* The post starts as a DRAFT - `published_at` is not settable here and no
|
|
411
|
+
* amount of arguments will publish it. Publishing is a second call, and it
|
|
412
|
+
* is {@link setPublished}, not {@link update}.
|
|
413
|
+
*
|
|
414
|
+
* Rides the general ceiling: there is no per-user cap on how many posts may
|
|
415
|
+
* be created, and no length cap beyond the model's 200 000 characters of
|
|
416
|
+
* markdown.
|
|
417
|
+
*
|
|
418
|
+
* @throws {OmsApiError} 401 `"Session required to access this resource."`;
|
|
419
|
+
* 400 with the validation sentence, most often the slug already existing
|
|
420
|
+
* in this blog.
|
|
421
|
+
*/
|
|
422
|
+
create(input: CreateBlogPostInput, options?: RequestOptions): Promise<BlogPost>;
|
|
423
|
+
/**
|
|
424
|
+
* `PATCH /blog_posts/:id` - edits a post's fields.
|
|
425
|
+
*
|
|
426
|
+
* Do NOT put `publish` in this body. The controller checks for it FIRST and,
|
|
427
|
+
* when it is a boolean or the string `"true"`/`"false"`, publishes or
|
|
428
|
+
* unpublishes and returns immediately - `ok!` raises the response - so every
|
|
429
|
+
* other field in the same request is discarded without a word. That is a
|
|
430
|
+
* silent data loss, not an error you can catch. Use {@link setPublished} for
|
|
431
|
+
* the flag and this method for the content; two calls, in either order.
|
|
432
|
+
*
|
|
433
|
+
* Always send `tags`, including when they have not changed. See
|
|
434
|
+
* {@link UpdateBlogPostInput}.
|
|
435
|
+
*
|
|
436
|
+
* Editing does not change `published_at`, so an edit to a published post
|
|
437
|
+
* stays published and does not move in the feed's ordering.
|
|
438
|
+
*
|
|
439
|
+
* @throws {OmsApiError} 404 `"Post not found"`; 401 `"Not your post"`; 400
|
|
440
|
+
* with the validation sentence.
|
|
441
|
+
*/
|
|
442
|
+
update(id: BlogPostId, input: UpdateBlogPostInput, options?: RequestOptions): Promise<BlogPost>;
|
|
443
|
+
/**
|
|
444
|
+
* `PATCH /blog_posts/:id` with `{ publish }` - the publish switch, on its
|
|
445
|
+
* own.
|
|
446
|
+
*
|
|
447
|
+
* Separated from {@link update} because the controller treats it as an
|
|
448
|
+
* early-return branch rather than as a field: a body carrying `publish`
|
|
449
|
+
* never reaches `post.update(post_params)`, so mixing the two loses the
|
|
450
|
+
* content edit. Sending it alone is the only safe way to use it.
|
|
451
|
+
*
|
|
452
|
+
* `publish: true` stamps `published_at` with the current time - and moves
|
|
453
|
+
* the post to the top of every `recent` ordering. Re-publishing an already
|
|
454
|
+
* published post is a no-op that still answers `200` with the ORIGINAL
|
|
455
|
+
* `published_at`, so this cannot be used to bump a post. `publish: false`
|
|
456
|
+
* clears `published_at`, which unlists the post everywhere and, for a
|
|
457
|
+
* non-owner, turns {@link get} into a 401.
|
|
458
|
+
*
|
|
459
|
+
* @throws {OmsApiError} 404 `"Post not found"`; 401 `"Not your post"`.
|
|
460
|
+
*/
|
|
461
|
+
setPublished(id: BlogPostId, publish: boolean, options?: RequestOptions): Promise<BlogPost>;
|
|
462
|
+
/**
|
|
463
|
+
* `DELETE /blog_posts/:id` - permanent. `204`, no body.
|
|
464
|
+
*
|
|
465
|
+
* Takes the attached `cover_image` with it (`dependent: :destroy`). There is
|
|
466
|
+
* no trash and no undo.
|
|
467
|
+
*
|
|
468
|
+
* @throws {OmsApiError} 404 `"Post not found"`; 401 `"Not your post"`.
|
|
469
|
+
*/
|
|
470
|
+
destroy(id: BlogPostId, options?: RequestOptions): Promise<void>;
|
|
471
|
+
}
|