Publish featured content as the space owner rather than as the relay (#564)
This commit is contained in:
parent
b6ee3504be
commit
fe15d36a45
9 changed files with 52 additions and 53 deletions
|
|
@ -100,7 +100,7 @@ only gate.
|
||||||
- `access.ts`: joining, invites, relay auth errors
|
- `access.ts`: joining, invites, relay auth errors
|
||||||
- `management.ts` (NIP-86 admin checks, bans), `roles.ts` (member roles)
|
- `management.ts` (NIP-86 admin checks, bans), `roles.ts` (member roles)
|
||||||
- `actionItems.ts`: the admin review queue (reports and pending joins)
|
- `actionItems.ts`: the admin review queue (reports and pending joins)
|
||||||
- `featured.ts` (relay-signed featured content), `roomPins.ts`, `commands.ts` (NIP-CD slash
|
- `featured.ts` (the space owner's featured content), `roomPins.ts`, `commands.ts` (NIP-CD slash
|
||||||
commands)
|
commands)
|
||||||
- `hosting.ts`: client for the hosting backend's HTTP API
|
- `hosting.ts`: client for the hosting backend's HTTP API
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ domain `Relay`. These fields drive protocol decisions:
|
||||||
| `hasNip(29)` | whether the space has rooms. Without it everything lives in the space chat: `makeSpaceEntryPath` (`src/app/routes.ts`), `shareEvent` (`src/app/share.ts`), room search, notification grouping, `SpaceMenuRooms` |
|
| `hasNip(29)` | whether the space has rooms. Without it everything lives in the space chat: `makeSpaceEntryPath` (`src/app/routes.ts`), `shareEvent` (`src/app/share.ts`), room search, notification grouping, `SpaceMenuRooms` |
|
||||||
| `hasNip(70)` | whether space content is marked protected (below) |
|
| `hasNip(70)` | whether space content is marked protected (below) |
|
||||||
| `self` | the relay's own pubkey, the trust anchor for relay-signed state |
|
| `self` | the relay's own pubkey, the trust anchor for relay-signed state |
|
||||||
|
| `pubkey` | the space's owner, who writes space-wide content that has no other author (`deriveUserIsSpaceOwner`) |
|
||||||
| `redirect_to` | the relay has moved. The space layout offers `SpaceRedirect`, which runs `roomLists.migrateRelay` and `goToMovedSpace` |
|
| `redirect_to` | the relay has moved. The space layout offers `SpaceRedirect`, which runs `roomLists.migrateRelay` and `goToMovedSpace` |
|
||||||
| `hasNip(50)`, `hasNip("BUD-02")`, `hasNip("9a")` | search, blossom uploads, push |
|
| `hasNip(50)`, `hasNip("BUD-02")`, `hasNip("9a")` | search, blossom uploads, push |
|
||||||
|
|
||||||
|
|
@ -53,13 +54,13 @@ members and pins, the space member list, and roles. Anyone can publish events of
|
||||||
readers must check the author. The welshman collections do: `Rooms` and every
|
readers must check the author. The welshman collections do: `Rooms` and every
|
||||||
`RelaySignedDerivedPlugin` (`RelayMemberLists`, `RelayRoles`, `RoomPinLists`) drop events whose
|
`RelaySignedDerivedPlugin` (`RelayMemberLists`, `RelayRoles`, `RoomPinLists`) drop events whose
|
||||||
author isn't the relay's `self`, and re-check when NIP-11 loads. For a relay-authored kind with no
|
author isn't the relay's `self`, and re-check when NIP-11 loads. For a relay-authored kind with no
|
||||||
plugin, read through `deriveRelaySignedEvents(url, filters)` in `src/app/repository.ts`, as
|
plugin, filter `deriveEventsForUrl(url, filters)` on that `self` key yourself rather than reading a
|
||||||
`src/app/featured.ts` does, rather than a bare `deriveEventsForUrl`.
|
bare `deriveEventsForUrl`.
|
||||||
|
|
||||||
Content the space owns is published as the relay: `command.publishAsRelay(url)` has the relay
|
The app signs everything it publishes with the user's own key. Space-wide content with no author of
|
||||||
sign the event with its own key through the NIP-86 `signevent` method, then sends it back. Featured
|
its own belongs to the space's owner, the pubkey NIP-11 names: featured content
|
||||||
content (`setFeaturedContent` in `src/app/featured.ts`) is written this way, and only users the
|
(`setFeaturedContent` in `src/app/featured.ts`) is published by that person and read back scoped to
|
||||||
relay allows to call `signevent` can write it.
|
them, so `deriveUserIsSpaceOwner(url)` is what shows the editor.
|
||||||
|
|
||||||
The library is written by its members. A shelf or a pin is signed with the member's own key and
|
The library is written by its members. A shelf or a pin is signed with the member's own key and
|
||||||
published to the space like any other space content, so the library reads every `PINBOARD` seen on
|
published to the space like any other space content, so the library reads every `PINBOARD` seen on
|
||||||
|
|
@ -170,12 +171,11 @@ relay's URL, each call signed with a fresh NIP-98 event. Every method resolves t
|
||||||
| `createRole`, `editRole`, `deleteRole`, `assignRole`, `unassignRole` | `RoleCreate`, `RoleEdit`, `SpaceRoleMenu`, `SpaceMemberRoles`, `RoleAddMembers` |
|
| `createRole`, `editRole`, `deleteRole`, `assignRole`, `unassignRole` | `RoleCreate`, `RoleEdit`, `SpaceRoleMenu`, `SpaceMemberRoles`, `RoleAddMembers` |
|
||||||
| `listClaims`, `createClaim` | `Access.prepareInvite` |
|
| `listClaims`, `createClaim` | `Access.prepareInvite` |
|
||||||
| `changeRelayName`, `changeRelayDescription`, `changeRelayIcon` | `SpaceEdit` |
|
| `changeRelayName`, `changeRelayDescription`, `changeRelayIcon` | `SpaceEdit` |
|
||||||
| `signEvent` | `Command.publishAsRelay` |
|
|
||||||
|
|
||||||
Admin status is inferred. A relay answers `supportedmethods` with everything it implements rather
|
Admin status is inferred. A relay answers `supportedmethods` with everything it implements rather
|
||||||
than what the caller may use, and refuses non-admins outright, so `deriveUserIsSpaceAdmin(url)`
|
than what the caller may use, and refuses non-admins outright, so `deriveUserIsSpaceAdmin(url)`
|
||||||
only means the list came back non-empty (re-checked at most every five minutes per URL). To gate
|
only means the list came back non-empty (re-checked at most every five minutes per URL). To gate
|
||||||
one capability, check the method (`$supportedMethods.includes("signevent")`) and still handle an
|
one capability, check the method (`$supportedMethods.includes("banpubkey")`) and still handle an
|
||||||
error from the call, since a listed method can be blocked for a particular user.
|
error from the call, since a listed method can be blocked for a particular user.
|
||||||
`deriveUserCanCreateRoom` adds `ROOM_CREATE_PERMISSION` grants to space admins.
|
`deriveUserCanCreateRoom` adds `ROOM_CREATE_PERMISSION` grants to space admins.
|
||||||
|
|
||||||
|
|
@ -215,7 +215,6 @@ Space content goes to the space relay and nowhere else. The writer's routes deci
|
||||||
| `writer.forceRoutes(relay(url))` | forces the relay, no `h` | space-wide content, NIP-43 requests |
|
| `writer.forceRoutes(relay(url))` | forces the relay, no `h` | space-wide content, NIP-43 requests |
|
||||||
| default routes | the user's outbox plus inboxes of `p`-tagged pubkeys | profile, lists, settings, anything outside a space |
|
| default routes | the user's outbox plus inboxes of `p`-tagged pubkeys | profile, lists, settings, anything outside a space |
|
||||||
| `command.publishToRelays(urls)` | ignores the writer's relays | reactions, deletes, reports, comments, replies |
|
| `command.publishToRelays(urls)` | ignores the writer's relays | reactions, deletes, reports, comments, replies |
|
||||||
| `command.publishAsRelay(url)` | the relay signs via NIP-86 `signevent` | space-owned content |
|
|
||||||
| `wraps.get().publish({event, recipients})` | a NIP-59 wrap per recipient, to their `MESSAGING_RELAYS` | DMs and DM reactions and deletes |
|
| `wraps.get().publish({event, recipients})` | a NIP-59 wrap per recipient, to their `MESSAGING_RELAYS` | DMs and DM reactions and deletes |
|
||||||
|
|
||||||
`validate()` throws when an `h` tag has no forced route, and every room and relay-membership writer
|
`validate()` throws when an `h` tag has no forced route, and every room and relay-membership writer
|
||||||
|
|
@ -258,7 +257,7 @@ it. Zaps and goals are off on iOS (`ENABLE_ZAPS` in `src/app/env.ts`).
|
||||||
4. If users sign it through a remote signer, add it to `NIP46_PERMS` in `src/app/nip46.ts`.
|
4. If users sign it through a remote signer, add it to `NIP46_PERMS` in `src/app/nip46.ts`.
|
||||||
5. If it is relay-scoped state that has to survive a reload, add it to the `kinds` map in
|
5. If it is relay-scoped state that has to survive a reload, add it to the `kinds` map in
|
||||||
`src/app/storage.ts`.
|
`src/app/storage.ts`.
|
||||||
6. If the relay signs it, read it through a `RelaySignedDerivedPlugin` or `deriveRelaySignedEvents`.
|
6. If the relay signs it, read it through a `RelaySignedDerivedPlugin`.
|
||||||
|
|
||||||
## Parsing and building events
|
## Parsing and building events
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,6 @@ space content be keyed by relay.
|
||||||
- `deriveEvent`, `deriveEvents`, `deriveEventsById`, `deriveIsDeleted`
|
- `deriveEvent`, `deriveEvents`, `deriveEventsById`, `deriveIsDeleted`
|
||||||
- relay-scoped: `deriveEventsForUrl`, `deriveEventsByIdForUrl`, `deriveEventsByIdByUrl`,
|
- relay-scoped: `deriveEventsForUrl`, `deriveEventsByIdForUrl`, `deriveEventsByIdByUrl`,
|
||||||
`getEventsForUrl`
|
`getEventsForUrl`
|
||||||
- `deriveRelaySignedEvents`, which keeps only events signed by the relay's NIP-11 `self` key
|
|
||||||
- `deriveLatestEvent`
|
- `deriveLatestEvent`
|
||||||
|
|
||||||
Use these for raw event queries. Welshman's `Events` plugin has the same surface returning
|
Use these for raw event queries. Welshman's `Events` plugin has the same surface returning
|
||||||
|
|
@ -352,7 +351,7 @@ if (error) {
|
||||||
```
|
```
|
||||||
|
|
||||||
`publish` sends to the writer's own routes, which is why the excerpt forces them with
|
`publish` sends to the writer's own routes, which is why the excerpt forces them with
|
||||||
`forceRoutes`. `publishToRelays(urls)` and `publishAsRelay(url)` override those routes instead.
|
`forceRoutes`. `publishToRelays(urls)` overrides those routes instead.
|
||||||
flotilla-model's "Which relays an event goes to" says which one each kind needs.
|
flotilla-model's "Which relays an event goes to" says which one each kind needs.
|
||||||
|
|
||||||
Plugin mutators already return a `Command`: `roomLists.get().addRelay(url).then(publish)`,
|
Plugin mutators already return a `Command`: `roomLists.get().addRelay(url).then(publish)`,
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ setNip55Plugin(NostrSignerPlugin)
|
||||||
|
|
||||||
All follow the same shape — `get(key)` (sync), `one(key)` (reactive, lazy-loads), `load(key)`/`forceLoad(key)` (promises), plus convenience accessors returning `Projection`. Resolve with `app.use(...)`.
|
All follow the same shape — `get(key)` (sync), `one(key)` (reactive, lazy-loads), `load(key)`/`forceLoad(key)` (promises), plus convenience accessors returning `Projection`. Resolve with `app.use(...)`.
|
||||||
|
|
||||||
Every mutation method (`create`/`update`/`follow`/`addRelay`/`setRelays`/etc.) is `async` and returns a **`Command`**, not a `Thunk` — it builds the event but does not publish it. Call `.publish()` (or `.publishAsRelay(url)`) on the result to actually send it. See [Commands](#commands-deferred-publishing) below.
|
Every mutation method (`create`/`update`/`follow`/`addRelay`/`setRelays`/etc.) is `async` and returns a **`Command`**, not a `Thunk` — it builds the event but does not publish it. Call `.publish()` on the result to actually send it. See [Commands](#commands-deferred-publishing) below.
|
||||||
|
|
||||||
| Plugin | Data | Notable accessors |
|
| Plugin | Data | Notable accessors |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -115,7 +115,7 @@ Every mutation method (`create`/`update`/`follow`/`addRelay`/`setRelays`/etc.) i
|
||||||
| `Pinboards` | kind-30067 pinboards (many per author, keyed by address) | `forAuthor(pk)`, `loadForAuthor(pk)`, `create(fields)`, `update(addr, fn)` → `Command` |
|
| `Pinboards` | kind-30067 pinboards (many per author, keyed by address) | `forAuthor(pk)`, `loadForAuthor(pk)`, `create(fields)`, `update(addr, fn)` → `Command` |
|
||||||
| `Pins` | kind-39067 pins (keyed by address; each pin has its own `d` tag) | `forBoard(addr)`, `forProfile(pk)`, `loadForBoard(addr)`, `loadForProfile(pk)`, `create`, `update`, `addToBoard`, `removeFromBoard` → `Command` |
|
| `Pins` | kind-39067 pins (keyed by address; each pin has its own `d` tag) | `forBoard(addr)`, `forProfile(pk)`, `loadForBoard(addr)`, `loadForProfile(pk)`, `create`, `update`, `addToBoard`, `removeFromBoard` → `Command` |
|
||||||
| `Relays` | NIP-11 relay info (HTTP) | `display(url)`, `hasNip(url, n)`, `hasNegentropy(url)`; `relaySearch` |
|
| `Relays` | NIP-11 relay info (HTTP) | `display(url)`, `hasNip(url, n)`, `hasNegentropy(url)`; `relaySearch` |
|
||||||
| `RelayManagement` | NIP-86 mgmt API | `forUrl(url)` → a `ManagementApi` client that signs auth as the app's user (`forUrl(url).signEvent(event)`, role/member ops, …) |
|
| `RelayManagement` | NIP-86 mgmt API | `forUrl(url)` → a `ManagementApi` client that signs auth as the app's user (role/member ops, ban/allow, …) |
|
||||||
| `RelayStats` | per-relay connection counters | `get(url)`, `getQuality(url)` (0–1, drives router ranking) |
|
| `RelayStats` | per-relay connection counters | `get(url)`, `getQuality(url)` (0–1, drives router ranking) |
|
||||||
| `RelayRoles` / `RelayMemberLists` / `RoomPinLists` | relay-signed state, keyed per relay | relay-scoped collections (see `RelaySignedDerivedPlugin`) |
|
| `RelayRoles` / `RelayMemberLists` / `RoomPinLists` | relay-signed state, keyed per relay | relay-scoped collections (see `RelaySignedDerivedPlugin`) |
|
||||||
| `Handles` | NIP-05 (HTTP, batched) | `forPubkey(pk)`, `display(nip05)`, `loadForPubkey(pk)` |
|
| `Handles` | NIP-05 (HTTP, batched) | `forPubkey(pk)`, `display(nip05)`, `loadForPubkey(pk)` |
|
||||||
|
|
@ -145,9 +145,9 @@ const writeRelays = app.use(RelayLists).writeUrls(pubkey).get() // string[]
|
||||||
// Mutations return a Command — build it, then decide how to publish it
|
// Mutations return a Command — build it, then decide how to publish it
|
||||||
const command = await app.use(RelayLists).addWriteUrl("wss://relay.example")
|
const command = await app.use(RelayLists).addWriteUrl("wss://relay.example")
|
||||||
command.publish() // normal outbox/relays flow via Thunks
|
command.publish() // normal outbox/relays flow via Thunks
|
||||||
// or: command.publishAsRelay("wss://relay.example") // sign + send straight to one relay (NIP-86 style)
|
// or: command.publishToRelays(["wss://relay.example"]) // send straight to one relay
|
||||||
|
|
||||||
// Since these methods are async, `publish`/`publishAsRelay` free functions avoid a double-await:
|
// Since these methods are async, `publish`/`publishToRelays` free functions avoid a double-await:
|
||||||
import {publish} from "@welshman/app"
|
import {publish} from "@welshman/app"
|
||||||
await app.use(RelayLists).addWriteUrl("wss://relay.example").then(publish)
|
await app.use(RelayLists).addWriteUrl("wss://relay.example").then(publish)
|
||||||
```
|
```
|
||||||
|
|
@ -203,21 +203,18 @@ command.relays // string[] — where publish() will send it
|
||||||
|
|
||||||
command.publish() // normal path: app.use(Thunks).publish({event, relays: command.relays})
|
command.publish() // normal path: app.use(Thunks).publish({event, relays: command.relays})
|
||||||
command.publishToRelays(urls) // publish to a specific relay set instead of command.relays
|
command.publishToRelays(urls) // publish to a specific relay set instead of command.relays
|
||||||
command.publishAsRelay(url) // NIP-86: the relay signs the event with its own key
|
|
||||||
// (signevent), then publish the relay-signed event back to `url`
|
|
||||||
command.signAsRelay(url) // just the NIP-86 signevent step (returns {result, error})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This lets a caller preview/log a command, choose a different transport, or drop it entirely, instead of every plugin method publishing unconditionally. `Wraps.publish` is the one exception — it fans a single rumor out to a `MergedThunk` of per-recipient wraps (each with its own relays), which doesn't fit the one-event/one-relay-set `Command` shape, so it still publishes directly.
|
This lets a caller preview/log a command, choose a different transport, or drop it entirely, instead of every plugin method publishing unconditionally. `Wraps.publish` is the one exception — it fans a single rumor out to a `MergedThunk` of per-recipient wraps (each with its own relays), which doesn't fit the one-event/one-relay-set `Command` shape, so it still publishes directly.
|
||||||
|
|
||||||
`publish`/`publishToRelays`/`publishAsRelay`/`signAsRelay` are also exported as free functions (e.g. `(command) => command.publish()`, `(url) => (command) => command.publishAsRelay(url)`) so you can chain straight off the mutation method's promise instead of double-awaiting:
|
`publish`/`publishToRelays` are also exported as free functions (e.g. `(command) => command.publish()`, `(urls) => (command) => command.publishToRelays(urls)`) so you can chain straight off the mutation method's promise instead of double-awaiting:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import {publish, publishAsRelay} from "@welshman/app"
|
import {publish, publishToRelays} from "@welshman/app"
|
||||||
|
|
||||||
await app.use(FollowLists).follow(["p", otherPubkey]).then(publish)
|
await app.use(FollowLists).follow(["p", otherPubkey]).then(publish)
|
||||||
await app.use(Rooms).leave(relayUrl, roomMeta).then(publish)
|
await app.use(Rooms).leave(relayUrl, roomMeta).then(publish)
|
||||||
await app.use(Rooms).join(relayUrl, roomMeta).then(publishAsRelay(relayUrl))
|
await app.use(Rooms).join(relayUrl, roomMeta).then(publishToRelays([relayUrl]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Requests & sync
|
## Requests & sync
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ callbacks and hot paths.
|
||||||
|
|
||||||
1. Build a writer: `app.use(Domain).writer(Kind, reader?)`, then chain its setters
|
1. Build a writer: `app.use(Domain).writer(Kind, reader?)`, then chain its setters
|
||||||
2. Wrap it: `const command = await app.use(Domain).command(writer)`
|
2. Wrap it: `const command = await app.use(Domain).command(writer)`
|
||||||
3. Publish it: `command.publish()`, `.publishToRelays(urls)`, or `.publishAsRelay(url)`
|
3. Publish it: `command.publish()` or `.publishToRelays(urls)`
|
||||||
4. Display thunk status to user (for cancel/error handling)
|
4. Display thunk status to user (for cancel/error handling)
|
||||||
|
|
||||||
Plugin mutators (`app.use(FollowLists).follow(...)`, `app.use(Rooms).joinRoom(...)`, …) already
|
Plugin mutators (`app.use(FollowLists).follow(...)`, `app.use(Rooms).joinRoom(...)`, …) already
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import EditFeaturedContent from "@app/components/EditFeaturedContent.svelte"
|
import EditFeaturedContent from "@app/components/EditFeaturedContent.svelte"
|
||||||
import {deriveFeaturedContent} from "@app/featured"
|
import {deriveFeaturedContent} from "@app/featured"
|
||||||
import {deriveUserIsSpaceAdmin} from "@app/management"
|
import {deriveUserIsSpaceOwner} from "@app/management"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
const {url}: Props = $props()
|
const {url}: Props = $props()
|
||||||
|
|
||||||
const content = deriveFeaturedContent(url)
|
const content = deriveFeaturedContent(url)
|
||||||
const canEdit = deriveUserIsSpaceAdmin(url)
|
const canEdit = deriveUserIsSpaceOwner(url)
|
||||||
|
|
||||||
const edit = () => pushModal(EditFeaturedContent, {url, initial: $content})
|
const edit = () => pushModal(EditFeaturedContent, {url, initial: $content})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,38 @@
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {APP_DATA, tagSpec, tagValues, relay} from "@welshman/util"
|
import {APP_DATA, relay, sortEventsDesc, tagSpec, tagValues} from "@welshman/util"
|
||||||
import {AppData} from "@welshman/domain"
|
import {AppData} from "@welshman/domain"
|
||||||
import {Domain} from "@welshman/app"
|
import {Relays, publish} from "@welshman/app"
|
||||||
import {app} from "@app/core"
|
import {command, fromApp, relays, writer} from "@app/core"
|
||||||
import {deriveRelaySignedEvents} from "@app/repository"
|
import {deriveEventsForUrl} from "@app/repository"
|
||||||
|
|
||||||
// NIP-78 app data published by the relay's self key. Each featured entry is a
|
// NIP-78 app data published by the space's owner, the pubkey its NIP-11 document names. Each
|
||||||
// ["content", <value>] tag (freeform text, intended to be a url or nevent).
|
// featured entry is a ["content", <value>] tag (freeform text, intended to be a url or nevent).
|
||||||
export const FEATURED_CONTENT_D = "flotilla/featured-content"
|
export const FEATURED_CONTENT_D = "flotilla/featured-content"
|
||||||
|
|
||||||
export const deriveFeaturedContent = (url: string) =>
|
export const deriveFeaturedContent = (url: string) =>
|
||||||
derived(
|
derived(
|
||||||
deriveRelaySignedEvents(url, [{kinds: [APP_DATA], "#d": [FEATURED_CONTENT_D]}]),
|
[
|
||||||
([event]) => tagValues(tagSpec("content"), event?.tags ?? []),
|
fromApp($app => $app.use(Relays).one(url)),
|
||||||
|
deriveEventsForUrl(url, [{kinds: [APP_DATA], "#d": [FEATURED_CONTENT_D]}]),
|
||||||
|
],
|
||||||
|
([$relay, $events]) => {
|
||||||
|
const [event] = sortEventsDesc($events.filter(e => e.pubkey === $relay?.pubkey))
|
||||||
|
|
||||||
|
return tagValues(tagSpec("content"), event?.tags ?? [])
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Publish the featured content list by asking the relay to sign it with its self
|
|
||||||
// key (the unofficial NIP-86 "signevent" method).
|
|
||||||
export const setFeaturedContent = async (url: string, content: string[]) => {
|
export const setFeaturedContent = async (url: string, content: string[]) => {
|
||||||
const tags = content
|
const tags = content
|
||||||
.map(value => value.trim())
|
.map(value => value.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map(value => ["content", value])
|
.map(value => ["content", value])
|
||||||
|
|
||||||
const writer = app
|
const eventWriter = writer(AppData)
|
||||||
.get()
|
|
||||||
.use(Domain)
|
|
||||||
.writer(AppData)
|
|
||||||
.forceRoutes(relay(url))
|
|
||||||
.setIdentifier(FEATURED_CONTENT_D)
|
.setIdentifier(FEATURED_CONTENT_D)
|
||||||
|
.setProtected(await relays.get().hasNip(url, 70))
|
||||||
|
.forceRoutes(relay(url))
|
||||||
.addTags(...tags)
|
.addTags(...tags)
|
||||||
|
|
||||||
const command = await app.get().use(Domain).command(writer)
|
return command(eventWriter).then(publish)
|
||||||
|
|
||||||
return command.publishAsRelay(url)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {derived, readable, writable} from "svelte/store"
|
import {derived, readable, writable} from "svelte/store"
|
||||||
import {ago, MINUTE, now, simpleCache} from "@welshman/lib"
|
import {ago, MINUTE, now, simpleCache} from "@welshman/lib"
|
||||||
import {ROOM_CREATE_PERMISSION, hexTags, tagValues} from "@welshman/util"
|
import {ROOM_CREATE_PERMISSION, hexTags, tagValues} from "@welshman/util"
|
||||||
|
import {Relays} from "@welshman/app"
|
||||||
import {fromApp, relayManagement, user} from "@app/core"
|
import {fromApp, relayManagement, user} from "@app/core"
|
||||||
import {deriveEventsForUrl} from "@app/repository"
|
import {deriveEventsForUrl} from "@app/repository"
|
||||||
|
|
||||||
|
|
@ -59,6 +60,14 @@ export const deriveSpaceSupportedMethods = (url?: string) =>
|
||||||
export const deriveUserIsSpaceAdmin = (url?: string) =>
|
export const deriveUserIsSpaceAdmin = (url?: string) =>
|
||||||
derived(deriveSpaceSupportedMethods(url), $methods => $methods.length > 0)
|
derived(deriveSpaceSupportedMethods(url), $methods => $methods.length > 0)
|
||||||
|
|
||||||
|
// The one identity a space names as its own, in its NIP-11 `pubkey`. Space-wide content with no
|
||||||
|
// author to scope it to belongs to that person.
|
||||||
|
export const deriveUserIsSpaceOwner = (url: string) =>
|
||||||
|
derived(
|
||||||
|
[user, fromApp($app => $app.use(Relays).one(url))],
|
||||||
|
([$user, $relay]) => $user.pubkey === $relay?.pubkey,
|
||||||
|
)
|
||||||
|
|
||||||
export const deriveUserCanCreateRoom = (url: string) =>
|
export const deriveUserCanCreateRoom = (url: string) =>
|
||||||
derived(
|
derived(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import {derived, readable} from "svelte/store"
|
import {readable} from "svelte/store"
|
||||||
import type {Unsubscriber} from "svelte/store"
|
import type {Unsubscriber} from "svelte/store"
|
||||||
import {filter, first, on, spec} from "@welshman/lib"
|
import {first, on} from "@welshman/lib"
|
||||||
import type {Maybe} from "@welshman/lib"
|
import type {Maybe} from "@welshman/lib"
|
||||||
import {sortEventsDesc} from "@welshman/util"
|
import {sortEventsDesc} from "@welshman/util"
|
||||||
import type {Filter, TrustedEvent} from "@welshman/util"
|
import type {Filter, TrustedEvent} from "@welshman/util"
|
||||||
import * as store from "@welshman/store"
|
import * as store from "@welshman/store"
|
||||||
import {Network, Relays} from "@welshman/app"
|
import {Network} from "@welshman/app"
|
||||||
import {app, fromApp} from "@app/core"
|
import {app, fromApp} from "@app/core"
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
|
|
@ -59,12 +59,6 @@ export const deriveEventsByIdByUrl = (filters: Filter[] = [{}]) =>
|
||||||
store.deriveEventsByIdByUrl({filters, tracker: $app.tracker, repository: $app.repository}),
|
store.deriveEventsByIdByUrl({filters, tracker: $app.tracker, repository: $app.repository}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const deriveRelaySignedEvents = (url: string, filters: Filter[] = [{}]) =>
|
|
||||||
derived(
|
|
||||||
[fromApp($app => $app.use(Relays).one(url)), deriveEventsForUrl(url, filters)],
|
|
||||||
([$relay, $events]) => filter(spec({pubkey: $relay?.self}), $events as TrustedEvent[]),
|
|
||||||
)
|
|
||||||
|
|
||||||
// The most recent event held from one author.
|
// The most recent event held from one author.
|
||||||
|
|
||||||
const latestByPubkey = new Map<string, Maybe<TrustedEvent>>()
|
const latestByPubkey = new Map<string, Maybe<TrustedEvent>>()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue