blossom/nsite tweaks.
This commit is contained in:
+7
-3
@@ -72,12 +72,16 @@ func ParseSiteManifest(event *nostr.Event) (*SiteManifest, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(sm.Paths) == 0 {
|
||||||
|
return sm, fmt.Errorf("nsite has zero paths listed")
|
||||||
|
}
|
||||||
|
|
||||||
return sm, nil
|
return sm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SiteManifest) ToEvent(pubkey nostr.PubKey) *nostr.Event {
|
func (sm SiteManifest) ToEvent() nostr.Event {
|
||||||
event := &nostr.Event{
|
event := nostr.Event{
|
||||||
PubKey: pubkey,
|
PubKey: sm.Pubkey,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Tags: nostr.Tags{},
|
Tags: nostr.Tags{},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package nip5a
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSiteManifest(t *testing.T) {
|
||||||
|
pubkey := nostr.MustPubKeyFromHex("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")
|
||||||
|
|
||||||
|
t.Run("root site", func(t *testing.T) {
|
||||||
|
event := &nostr.Event{
|
||||||
|
Kind: nostr.KindNsiteRoot,
|
||||||
|
PubKey: pubkey,
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"path", "/index.html", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"},
|
||||||
|
{"path", "/about.html", "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"},
|
||||||
|
{"path", "/favicon.ico", "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"},
|
||||||
|
{"server", "https://blossom.example.com"},
|
||||||
|
{"title", "My Nostr Site"},
|
||||||
|
{"description", "A static website hosted on Nostr"},
|
||||||
|
{"source", "https://github.com/example/my-nostr-site"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sm, err := ParseSiteManifest(event)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, sm.Root)
|
||||||
|
assert.Equal(t, pubkey, sm.Pubkey)
|
||||||
|
assert.Equal(t, "My Nostr Site", sm.Title)
|
||||||
|
assert.Equal(t, "A static website hosted on Nostr", sm.Description)
|
||||||
|
assert.Equal(t, "https://github.com/example/my-nostr-site", sm.Source)
|
||||||
|
assert.Len(t, sm.Paths, 3)
|
||||||
|
assert.Len(t, sm.Servers, 1)
|
||||||
|
assert.Equal(t, "https://blossom.example.com", sm.Servers[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("named site", func(t *testing.T) {
|
||||||
|
event := &nostr.Event{
|
||||||
|
Kind: nostr.KindNsiteNamed,
|
||||||
|
PubKey: pubkey,
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"d", "blog"},
|
||||||
|
{"path", "/index.html", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"},
|
||||||
|
{"path", "/post.html", "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"},
|
||||||
|
{"server", "https://blossom.example.com"},
|
||||||
|
{"title", "My Blog"},
|
||||||
|
{"description", "A blog hosted on Nostr"},
|
||||||
|
{"source", "https://github.com/example/my-nostr-blog"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sm, err := ParseSiteManifest(event)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, sm.Root)
|
||||||
|
assert.Equal(t, "blog", sm.Identifier)
|
||||||
|
assert.Equal(t, pubkey, sm.Pubkey)
|
||||||
|
assert.Equal(t, "My Blog", sm.Title)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing d tag on named site", func(t *testing.T) {
|
||||||
|
event := &nostr.Event{
|
||||||
|
Kind: nostr.KindNsiteNamed,
|
||||||
|
PubKey: pubkey,
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"path", "/index.html", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseSiteManifest(event)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "missing d tag")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid kind", func(t *testing.T) {
|
||||||
|
event := &nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
PubKey: pubkey,
|
||||||
|
Tags: nostr.Tags{},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseSiteManifest(event)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid site manifest kind")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHashForPath(t *testing.T) {
|
||||||
|
pubkey := nostr.MustPubKeyFromHex("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")
|
||||||
|
event := &nostr.Event{
|
||||||
|
Kind: nostr.KindNsiteRoot,
|
||||||
|
PubKey: pubkey,
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"path", "/index.html", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"},
|
||||||
|
{"path", "/about.html", "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sm, err := ParseSiteManifest(event)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hash, ok := sm.GetHashForPath("/index.html")
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99", hex.EncodeToString(hash[:]))
|
||||||
|
|
||||||
|
_, ok = sm.GetHashForPath("/nonexistent.html")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizePath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"/index.html", "/index.html"},
|
||||||
|
{"/about.html", "/about.html"},
|
||||||
|
{"/blog/", "/blog/index.html"},
|
||||||
|
{"/", "/index.html"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result := NormalizePath(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPubKeyBase36(t *testing.T) {
|
||||||
|
pubkey := nostr.MustPubKeyFromHex("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")
|
||||||
|
|
||||||
|
b36 := PubKeyToBase36(pubkey)
|
||||||
|
assert.Len(t, b36, 50)
|
||||||
|
|
||||||
|
decoded, err := PubKeyFromBase36(b36)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, pubkey, decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeSiteURL(t *testing.T) {
|
||||||
|
pubkey := nostr.MustPubKeyFromHex("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")
|
||||||
|
|
||||||
|
t.Run("npub root site", func(t *testing.T) {
|
||||||
|
decodedPubkey, identifier, isRoot, err := DecodeSiteURL("npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, isRoot)
|
||||||
|
assert.Equal(t, "", identifier)
|
||||||
|
assert.Equal(t, decodedPubkey, pubkey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("named site", func(t *testing.T) {
|
||||||
|
b36 := PubKeyToBase36(pubkey)
|
||||||
|
label := b36 + "blog"
|
||||||
|
|
||||||
|
decodedPubkey, identifier, isRoot, err := DecodeSiteURL(label)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, isRoot)
|
||||||
|
assert.Equal(t, "blog", identifier)
|
||||||
|
assert.Equal(t, decodedPubkey, pubkey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("strips domain suffix", func(t *testing.T) {
|
||||||
|
b36 := PubKeyToBase36(pubkey)
|
||||||
|
label := b36 + "blog.nsite-host.com"
|
||||||
|
|
||||||
|
_, identifier, _, err := DecodeSiteURL(label)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "blog", identifier)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid dtag format", func(t *testing.T) {
|
||||||
|
b36 := PubKeyToBase36(pubkey)
|
||||||
|
label := b36 + "Blog"
|
||||||
|
|
||||||
|
_, _, _, err := DecodeSiteURL(label)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid dtag format")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("label too short", func(t *testing.T) {
|
||||||
|
_, _, _, err := DecodeSiteURL("npub1")
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ends with dash", func(t *testing.T) {
|
||||||
|
b36 := PubKeyToBase36(pubkey)
|
||||||
|
label := b36 + "blog-"
|
||||||
|
|
||||||
|
_, _, _, err := DecodeSiteURL(label)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid site label format")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSiteManifestToEvent(t *testing.T) {
|
||||||
|
pubkey := nostr.MustPubKeyFromHex("266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5")
|
||||||
|
|
||||||
|
sm := &SiteManifest{
|
||||||
|
Root: true,
|
||||||
|
Pubkey: pubkey,
|
||||||
|
Identifier: "",
|
||||||
|
Paths: map[string][32]byte{
|
||||||
|
"/index.html": mustHash("186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"),
|
||||||
|
},
|
||||||
|
Servers: []string{"https://blossom.example.com"},
|
||||||
|
Title: "Test Site",
|
||||||
|
Description: "A test site",
|
||||||
|
Source: "https://github.com/example/test",
|
||||||
|
}
|
||||||
|
|
||||||
|
event := sm.ToEvent()
|
||||||
|
assert.Equal(t, nostr.KindNsiteRoot, event.Kind)
|
||||||
|
assert.Equal(t, pubkey, event.PubKey)
|
||||||
|
|
||||||
|
sm.Root = false
|
||||||
|
sm.Identifier = "blog"
|
||||||
|
event = sm.ToEvent()
|
||||||
|
assert.Equal(t, nostr.KindNsiteNamed, event.Kind)
|
||||||
|
found := false
|
||||||
|
for _, tag := range event.Tags {
|
||||||
|
if tag[0] == "d" && tag[1] == "blog" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustHash(s string) [32]byte {
|
||||||
|
var h [32]byte
|
||||||
|
b, _ := hex.DecodeString(s)
|
||||||
|
copy(h[:], b)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@ package blossom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -11,19 +12,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Download downloads a file from the media server by its hash
|
// Download downloads a file from the media server by its hash
|
||||||
func (c *Client) Download(ctx context.Context, hash string) ([]byte, error) {
|
func (c *Client) Download(ctx context.Context, hash [32]byte) ([]byte, error) {
|
||||||
if !nostr.IsValid32ByteHex(hash) {
|
hhash := hex.EncodeToString(hash[:])
|
||||||
return nil, fmt.Errorf("%s is not a valid 32-byte hex string", hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", c.mediaserver+"/"+hash, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", c.mediaserver+"/"+hhash, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authHeader := c.authorizationHeader(ctx, func(evt *nostr.Event) {
|
authHeader := c.authorizationHeader(ctx, func(evt *nostr.Event) {
|
||||||
evt.Tags = append(evt.Tags, nostr.Tag{"t", "get"})
|
evt.Tags = append(evt.Tags, nostr.Tag{"t", "get"})
|
||||||
evt.Tags = append(evt.Tags, nostr.Tag{"x", hash})
|
evt.Tags = append(evt.Tags, nostr.Tag{"x", hhash})
|
||||||
})
|
})
|
||||||
req.Header.Add("Authorization", authHeader)
|
req.Header.Add("Authorization", authHeader)
|
||||||
|
|
||||||
@@ -41,19 +40,17 @@ func (c *Client) Download(ctx context.Context, hash string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DownloadToFile downloads a file from the media server and saves it to the specified path
|
// DownloadToFile downloads a file from the media server and saves it to the specified path
|
||||||
func (c *Client) DownloadToFile(ctx context.Context, hash string, filePath string) error {
|
func (c *Client) DownloadToFile(ctx context.Context, hash [32]byte, filePath string) error {
|
||||||
if !nostr.IsValid32ByteHex(hash) {
|
hhash := hex.EncodeToString(hash[:])
|
||||||
return fmt.Errorf("%s is not a valid 32-byte hex string", hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", c.mediaserver+"/"+hash, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", c.mediaserver+"/"+hhash, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authHeader := c.authorizationHeader(ctx, func(evt *nostr.Event) {
|
authHeader := c.authorizationHeader(ctx, func(evt *nostr.Event) {
|
||||||
evt.Tags = append(evt.Tags, nostr.Tag{"t", "get"})
|
evt.Tags = append(evt.Tags, nostr.Tag{"t", "get"})
|
||||||
evt.Tags = append(evt.Tags, nostr.Tag{"x", hash})
|
evt.Tags = append(evt.Tags, nostr.Tag{"x", hhash})
|
||||||
})
|
})
|
||||||
req.Header.Add("Authorization", authHeader)
|
req.Header.Add("Authorization", authHeader)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user