docs/signals.md says what the admin's setting does and that a key is checked before it is saved; the install guide lists TYPESAFE_KEY; the API reference has Signals' routes and types and the new fields of Me and Settings; the home page says an admin may provide the key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
79
site/api.mjs
79
site/api.mjs
@@ -67,9 +67,11 @@ export const ipx = {
|
||||
},
|
||||
{
|
||||
method: 'PATCH', path: '/api/me', slug: 'patch-me',
|
||||
summary: 'Change the words that hide an item in every feed you read.',
|
||||
summary: 'Change the words that hide an item in every feed you read, or your TypeSafe key for Signals.',
|
||||
doc: '<p>A key is checked with TypeSafe, by one short question on it, before it is saved.</p>',
|
||||
body: 'MePatch', returns: '204 No Content',
|
||||
errors: [['400 Bad Request', 'More than 500 words, or one over 200 characters.']],
|
||||
errors: [['400 Bad Request', 'More than 500 words, or one over 200 characters; or a key TypeSafe did not accept.'],
|
||||
['502 Bad Gateway', 'TypeSafe could not be asked about the key just now. It is not saved.']],
|
||||
example: ipxCurl('PATCH', '/api/me', '{"blocked":["sponsor","ad read"]}'),
|
||||
},
|
||||
{
|
||||
@@ -205,6 +207,54 @@ export const ipx = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'signals', name: 'signals', title: 'Signals',
|
||||
summary: 'Subjects picked out of every feed you subscribe to by TypeSafe.',
|
||||
items: [
|
||||
{
|
||||
method: 'GET', path: '/api/signals', slug: 'get-signals',
|
||||
summary: 'Your Signals, each with its items and unread counted.',
|
||||
returns: 'Vec<Signal>',
|
||||
example: ipxCurl('GET', '/api/signals'),
|
||||
},
|
||||
{
|
||||
method: 'POST', path: '/api/signals', slug: 'post-signals',
|
||||
summary: 'Make a Signal. It looks back over the last 30 days, then at each new item.',
|
||||
doc: '<p>Each item is one call to TypeSafe: on the server’s key when an admin has chosen that (<code>signals_on_server</code>), otherwise on yours, saved with <a href="/docs/api/ipx/auth/patch-me"><code>PATCH /api/me</code></a>. Without a key, nothing is picked out.</p>',
|
||||
body: 'SignalBody', returns: '{ id: i64 }',
|
||||
errors: [['400 Bad Request', 'No name or one over 100 characters, not 1 to 50 words, a description over 500 characters, or an icon that is not a style and a name.']],
|
||||
example: ipxCurl('POST', '/api/signals', '{"name":"Formula 1","keywords":["formula 1","f1"],"description":"Grand prix racing, not road cars."}'),
|
||||
},
|
||||
{
|
||||
method: 'PATCH', path: '/api/signals/{id}', slug: 'patch-signal',
|
||||
summary: 'Change a Signal. A new name, words or description reads everything again; a new icon does not.',
|
||||
pathParams: [['id', 'i64', 'The Signal’s id.']],
|
||||
body: 'SignalBody', returns: '204 No Content',
|
||||
errors: [['400 Bad Request', 'As for a new Signal.'], ['404 Not Found', 'No Signal of yours has that id.']],
|
||||
example: ipxCurl('PATCH', '/api/signals/3', '{"keywords":["f1","grand prix"]}'),
|
||||
},
|
||||
{
|
||||
method: 'DELETE', path: '/api/signals/{id}', slug: 'delete-signal',
|
||||
summary: 'Delete a Signal. Its items stay in their feeds.',
|
||||
pathParams: [['id', 'i64', 'The Signal’s id.']],
|
||||
returns: '204 No Content', errors: [['404 Not Found', 'No Signal of yours has that id.']],
|
||||
},
|
||||
{
|
||||
method: 'GET', path: '/api/signals/{id}/entries', slug: 'get-signal-entries',
|
||||
summary: 'A page of the items picked out for a Signal, as a feed’s list is.',
|
||||
pathParams: [['id', 'i64', 'The Signal’s id.']],
|
||||
query: 'Page', returns: 'EntryPage', errors: [['404 Not Found', 'No Signal of yours has that id.']],
|
||||
example: ipxCurl('GET', '/api/signals/3/entries?filter=unread&limit=10'),
|
||||
},
|
||||
{
|
||||
method: 'POST', path: '/api/signals/icon', slug: 'post-signal-icon',
|
||||
summary: 'Have TypeSafe choose a Signal’s icon from the ones offered, on the server’s TypeSafe key.',
|
||||
body: 'IconAsk', returns: '{ icon: String }',
|
||||
errors: [['400 Bad Request', 'No icons, more than 60, or one that is not a style and a name.'],
|
||||
['404 Not Found', 'The server has no TypeSafe key.'], ['502 Bad Gateway', 'TypeSafe could not choose just now.']],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'files', name: 'files', title: 'Files',
|
||||
summary: 'Downloading, deleting and playing an episode’s file.',
|
||||
@@ -379,8 +429,27 @@ export const ipx = {
|
||||
['sign_out', 'Option<String>', 'Where signing out should go, for someone single sign-on signed in.'],
|
||||
['deadair_saved', 'i64', 'Seconds DeadAir has saved you, all told.'],
|
||||
['version', 'String', 'ipx’s version.'],
|
||||
['typesafe_key_set', 'bool', 'You have a TypeSafe key saved for Signals. The key itself is never sent back.'],
|
||||
['signals', 'String', 'Whose key Signals are judged on: <code>server</code>, for everyone, or <code>own</code>, each person’s.'],
|
||||
] },
|
||||
{ name: 'MePatch', doc: 'What <code>PATCH /api/me</code> changes.', fields: [['blocked', 'Option<Vec<String>>', 'Replaces the list.'],
|
||||
['typesafe_key', 'Option<Option<String>>', 'Your TypeSafe key for Signals, checked with TypeSafe and then saved; <code>null</code> or empty forgets it.']] },
|
||||
{ name: 'Signal', doc: 'A Signal in your sidebar.', fields: [
|
||||
['id', 'i64', ''], ['name', 'String', ''], ['keywords', 'Vec<String>', 'The words it listens for.'],
|
||||
['description', 'Option<String>', 'What you mean, in your own words.'],
|
||||
['icon', 'Option<String>', 'A Font Awesome icon, such as <code>solid/robot</code>; none shows its initials.'],
|
||||
['items', 'i64', 'Items picked out that are still in a feed you subscribe to.'], ['unread', 'i64', 'Those of them not read.'],
|
||||
] },
|
||||
{ name: 'SignalBody', doc: 'A Signal to make, or what to change of one. Leave out what stays.', fields: [
|
||||
['name', 'Option<String>', 'Up to 100 characters; needed for a new one.'],
|
||||
['keywords', 'Option<Vec<String>>', '1 to 50 words, each up to 200 characters; needed for a new one.'],
|
||||
['description', 'Option<Option<String>>', 'Up to 500 characters; <code>null</code> or empty clears it.'],
|
||||
['icon', 'Option<Option<String>>', '<code>solid/</code>, <code>regular/</code> or <code>brands/</code> and a name; <code>null</code> or empty clears it.'],
|
||||
] },
|
||||
{ name: 'IconAsk', doc: 'The icons to choose a Signal’s from.', fields: [
|
||||
['name', 'String', ''], ['keywords', 'Vec<String>', ''], ['description', 'Option<String>', ''],
|
||||
['candidates', 'Vec<(String, String)>', '1 to 60 of <code>[icon, label]</code>, the label its name and search terms.'],
|
||||
] },
|
||||
{ name: 'MePatch', doc: 'What <code>PATCH /api/me</code> changes.', fields: [['blocked', 'Option<Vec<String>>', 'Replaces the list.']] },
|
||||
{ name: 'Token', doc: 'An API token, as listed.', fields: [['id', 'i64', ''], ['name', 'String', ''], ['created', 'i64', ''], ['last_used', 'Option<i64>', '']] },
|
||||
{ name: 'NewToken', doc: 'A token to make.', fields: [['name', 'String', 'What it is for, to know it by when revoking it.']] },
|
||||
{ name: 'NewTokenMade', doc: 'A token just made: the only time it is shown.', fields: [['id', 'i64', ''], ['name', 'String', ''], ['token', 'String', 'Begins <code>ipx_</code>. Send it as <code>Authorization: Bearer</code>.']] },
|
||||
@@ -493,6 +562,7 @@ export const ipx = {
|
||||
['download_error', '{ feed, enclosure, msg, url }', ''],
|
||||
['deadair_done', '{ feed, enclosure, saved_s }', 'DeadAir finished a file.'],
|
||||
['scan_done', '{ feeds }', 'A check finished; how many feeds it looked at.'],
|
||||
['signal_hits', '{ user, hits }', 'Items were picked out for someone’s Signals; theirs to fetch again.'],
|
||||
] },
|
||||
{ name: 'Settings', doc: 'The server’s settings.', fields: [
|
||||
['schedule', 'String', 'How often feeds are checked, such as <code>every 60m</code>.'], ['every_mins', 'u64', 'The same in minutes.'],
|
||||
@@ -502,12 +572,15 @@ export const ipx = {
|
||||
['max_age_days', 'u64', '0 for no limit.'], ['art_cache_mb', 'u64', 'Artwork kept.'],
|
||||
['directory_url', 'String', ''], ['directory_token_set', 'bool', 'The token itself is never sent back.'],
|
||||
['directory_share', 'bool', ''],
|
||||
['signals_on_server', 'bool', 'Everyone’s Signals are judged on the server’s TypeSafe key, not each person’s.'],
|
||||
['typesafe_server_key', 'bool', 'The server has a TypeSafe key (<code>TYPESAFE_KEY</code>); without one, <code>signals_on_server</code> changes nothing.'],
|
||||
] },
|
||||
{ name: 'SettingsPatch', doc: 'What <code>PATCH /api/settings</code> changes. Leave out what stays.', fields: [
|
||||
['schedule', 'Option<String>', ''], ['max_new_per_check', 'Option<usize>', ''], ['media_types', 'Option<Vec<String>>', ''],
|
||||
['max_total_gb', 'Option<f64>', ''], ['max_age_days', 'Option<u64>', ''], ['art_cache_mb', 'Option<u64>', ''],
|
||||
['directory_url', 'Option<String>', 'Empty for no directory.'], ['directory_token', 'Option<String>', 'Replaces it; empty clears it.'],
|
||||
['directory_share', 'Option<bool>', ''],
|
||||
['signals_on_server', 'Option<bool>', 'Judge everyone’s Signals on the server’s TypeSafe key.'],
|
||||
] },
|
||||
{ name: 'UserRow', doc: 'An account.', fields: [['id', 'i64', ''], ['name', 'String', ''], ['admin', 'bool', ''], ['password', 'bool', 'Has one; without, it signs in only by single sign-on.'], ['created', 'Option<i64>', ''], ['last_login', 'Option<i64>', '']] },
|
||||
{ name: 'NewUser', doc: '', fields: [['name', 'String', 'No commas, semicolons or line breaks.'], ['password', 'String', 'Empty for single sign-on only.'], ['admin', 'bool', '']] },
|
||||
|
||||
@@ -50,6 +50,7 @@ docker build -f Dockerfile.web -t ipx-web .</code></pre>
|
||||
<tbody>
|
||||
<tr><td><code>IPX_DATABASE_URL</code></td><td>A <code>postgres://user:password@host:5432/database</code> address, to keep everything in Postgres instead of SQLite</td></tr>
|
||||
<tr><td><code>PUID</code>, <code>PGID</code></td><td>The user and group that own the files ipx writes</td></tr>
|
||||
<tr><td><code>TYPESAFE_KEY</code></td><td>The server's own <a href="https://typesafe.ai">typesafe.ai</a> API key: it picks a podcast's Directory category and a Signal's icon, and, when an admin turns it on, pays for everyone's Signals</td></tr>
|
||||
<tr><td><code>IPX_LOG_FORMAT</code></td><td><code>json</code> logs one JSON object a line, for Loki and the like</td></tr>
|
||||
<tr><td><code>OTEL_EXPORTER_OTLP_ENDPOINT</code></td><td>Sends traces of requests, checks and downloads to an OpenTelemetry collector; set <code>WEB_TRACING=on</code> on ipx-web too</td></tr>
|
||||
<tr><td><code>IPX_UPSTREAM</code></td><td>On ipx-web: where ipx is, <code>ipx:8099</code> by default</td></tr>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<div class="feature">
|
||||
<div class="say">
|
||||
<h3>Tune out the noise</h3>
|
||||
<p>Your feeds bring in more than anyone has time for. A Signal listens across all of them for one subject: give it a name, the words to listen for, and a sentence saying what you mean if the words are not enough. What fits lands in a list of its own, starting with the last month, and it keeps listening. To use Signals you will need an API key from <a href="https://typesafe.ai">typesafe.ai</a>.</p>
|
||||
<p>Your feeds bring in more than anyone has time for. A Signal listens across all of them for one subject: give it a name, the words to listen for, and a sentence saying what you mean if the words are not enough. What fits lands in a list of its own, starting with the last month, and it keeps listening. To use Signals you will need an API key from <a href="https://typesafe.ai">typesafe.ai</a>, unless your server's admin provides one.</p>
|
||||
</div>
|
||||
<figure class="frame"><img class="cut" src="/shots/signals.webp" width="874" height="398" loading="lazy"
|
||||
alt="A Signal called Space, picked out of the noise of eight feeds: episodes and posts about galaxies, Artemis II, Mars and a rocket launch from Hackaday, Universe Today, NASA's Houston We Have a Podcast and Ars Technica."></figure>
|
||||
|
||||
Reference in New Issue
Block a user