Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
3a4bdd9b0a
  1. 2
      cmd/server/main.go
  2. 17
      internal/generator/html.go
  3. 117
      internal/nostr/profile.go
  4. 23
      internal/server/handlers.go
  5. 6
      internal/server/server.go
  6. 3
      static/GitCitadel_Icon_Black.svg
  7. 2
      templates/base.html
  8. 47
      templates/contact.html

2
cmd/server/main.go

@ -87,7 +87,7 @@ func main() {
rewarmer.Start(ctx) rewarmer.Start(ctx)
// Initialize HTTP server // Initialize HTTP server
httpServer := server.NewServer(cfg.Server.Port, pageCache, feedCache, issueService, cfg.RepoAnnouncement, htmlGenerator) httpServer := server.NewServer(cfg.Server.Port, pageCache, feedCache, issueService, cfg.RepoAnnouncement, htmlGenerator, nostrClient)
// Start server in goroutine // Start server in goroutine
go func() { go func() {

17
internal/generator/html.go

@ -342,7 +342,7 @@ func (g *HTMLGenerator) GenerateEBooksPage(ebooks []EBookInfo, feedItems []FeedI
} }
// GenerateContactPage generates the contact form page // GenerateContactPage generates the contact form page
func (g *HTMLGenerator) GenerateContactPage(success bool, errorMsg string, eventID string, formData map[string]string, repoAnnouncement *nostr.RepoAnnouncement, feedItems []FeedItemInfo) (string, error) { func (g *HTMLGenerator) GenerateContactPage(success bool, errorMsg string, eventID string, formData map[string]string, repoAnnouncement *nostr.RepoAnnouncement, feedItems []FeedItemInfo, profile *nostr.Profile) (string, error) {
// Prepare form data with defaults // Prepare form data with defaults
subject := "" subject := ""
content := "" content := ""
@ -447,6 +447,21 @@ func (g *HTMLGenerator) GenerateContactPage(success bool, errorMsg string, event
} }
} }
// Add profile data
if profile != nil {
templateData["Profile"] = map[string]interface{}{
"Pubkey": profile.Pubkey,
"Name": profile.Name,
"DisplayName": profile.DisplayName,
"About": profile.About,
"Picture": profile.Picture,
"Website": profile.Website,
"NIP05": profile.NIP05,
"Lud16": profile.Lud16,
"Banner": profile.Banner,
}
}
// Execute base.html which will use the blocks from contact.html // Execute base.html which will use the blocks from contact.html
var buf bytes.Buffer var buf bytes.Buffer
if err := renderTmpl.ExecuteTemplate(&buf, "base.html", templateData); err != nil { if err := renderTmpl.ExecuteTemplate(&buf, "base.html", templateData); err != nil {

117
internal/nostr/profile.go

@ -0,0 +1,117 @@
package nostr
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
// Profile represents a parsed kind 0 profile event
type Profile struct {
Event *nostr.Event
Pubkey string
Name string
DisplayName string
About string
Picture string
Website string
NIP05 string
Lud16 string
Banner string
RawJSON map[string]interface{} // Store all other fields
}
// FetchProfile fetches a kind 0 profile event from an npub
func (c *Client) FetchProfile(ctx context.Context, npub string) (*Profile, error) {
// Decode npub to get pubkey
prefix, value, err := nip19.Decode(npub)
if err != nil {
return nil, fmt.Errorf("failed to decode npub: %w", err)
}
if prefix != "npub" {
return nil, fmt.Errorf("invalid npub prefix: expected 'npub', got '%s'", prefix)
}
pubkey, ok := value.(string)
if !ok {
return nil, fmt.Errorf("failed to parse npub: unexpected type")
}
// Create filter for kind 0 profile event
filter := nostr.Filter{
Kinds: []int{0},
Authors: []string{pubkey},
Limit: 1,
}
log.Printf("Fetching profile for npub %s (pubkey: %s)", npub, pubkey)
// Fetch the event
event, err := c.FetchEvent(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to fetch profile event: %w", err)
}
// Parse the profile
profile, err := ParseProfile(event)
if err != nil {
return nil, fmt.Errorf("failed to parse profile: %w", err)
}
return profile, nil
}
// ParseProfile parses a kind 0 profile event
func ParseProfile(event *nostr.Event) (*Profile, error) {
if event.Kind != 0 {
return nil, fmt.Errorf("expected kind 0, got %d", event.Kind)
}
profile := &Profile{
Event: event,
Pubkey: event.PubKey,
RawJSON: make(map[string]interface{}),
}
// Parse JSON content
var content map[string]interface{}
if err := json.Unmarshal([]byte(event.Content), &content); err != nil {
return nil, fmt.Errorf("failed to parse profile JSON: %w", err)
}
// Extract common fields
if name, ok := content["name"].(string); ok {
profile.Name = name
}
if displayName, ok := content["display_name"].(string); ok {
profile.DisplayName = displayName
}
if about, ok := content["about"].(string); ok {
profile.About = about
}
if picture, ok := content["picture"].(string); ok {
profile.Picture = picture
}
if website, ok := content["website"].(string); ok {
profile.Website = website
}
if nip05, ok := content["nip05"].(string); ok {
profile.NIP05 = nip05
}
if lud16, ok := content["lud16"].(string); ok {
profile.Lud16 = lud16
}
if banner, ok := content["banner"].(string); ok {
profile.Banner = banner
}
// Store all fields in RawJSON for access to any other fields
profile.RawJSON = content
return profile, nil
}

23
internal/server/handlers.go

@ -120,8 +120,19 @@ func (s *Server) handleContact(w http.ResponseWriter, r *http.Request) {
} }
} }
// Fetch profile for npub
var profile *nostr.Profile
npub := "npub1s3ht77dq4zqnya8vjun5jp3p44pr794ru36d0ltxu65chljw8xjqd975wz"
if s.nostrClient != nil {
profile, err = s.nostrClient.FetchProfile(ctx, npub)
if err != nil {
log.Printf("Failed to fetch profile for contact page: %v", err)
// Continue without profile - not critical
}
}
// Render the contact form (feed items not needed - only on landing page) // Render the contact form (feed items not needed - only on landing page)
html, err := s.htmlGenerator.GenerateContactPage(false, "", "", nil, repoAnnouncement, []generator.FeedItemInfo{}) html, err := s.htmlGenerator.GenerateContactPage(false, "", "", nil, repoAnnouncement, []generator.FeedItemInfo{}, profile)
if err != nil { if err != nil {
http.Error(w, "Failed to generate contact page", http.StatusInternalServerError) http.Error(w, "Failed to generate contact page", http.StatusInternalServerError)
return return
@ -138,7 +149,7 @@ func (s *Server) handleContact(w http.ResponseWriter, r *http.Request) {
// Parse form data // Parse form data
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
html, _ := s.htmlGenerator.GenerateContactPage(false, "Failed to parse form data", "", nil, nil, []generator.FeedItemInfo{}) html, _ := s.htmlGenerator.GenerateContactPage(false, "Failed to parse form data", "", nil, nil, []generator.FeedItemInfo{}, nil)
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html)) w.Write([]byte(html))
return return
@ -155,7 +166,7 @@ func (s *Server) handleContact(w http.ResponseWriter, r *http.Request) {
"content": content, "content": content,
"labels": labelsStr, "labels": labelsStr,
} }
html, _ := s.htmlGenerator.GenerateContactPage(false, "Subject and message are required", "", formData, nil, []generator.FeedItemInfo{}) html, _ := s.htmlGenerator.GenerateContactPage(false, "Subject and message are required", "", formData, nil, []generator.FeedItemInfo{}, nil)
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html)) w.Write([]byte(html))
return return
@ -185,7 +196,7 @@ func (s *Server) handleContact(w http.ResponseWriter, r *http.Request) {
"content": content, "content": content,
"labels": labelsStr, "labels": labelsStr,
} }
html, _ := s.htmlGenerator.GenerateContactPage(false, "Failed to connect to repository. Please try again later.", "", formData, nil, []generator.FeedItemInfo{}) html, _ := s.htmlGenerator.GenerateContactPage(false, "Failed to connect to repository. Please try again later.", "", formData, nil, []generator.FeedItemInfo{}, nil)
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html)) w.Write([]byte(html))
return return
@ -207,14 +218,14 @@ func (s *Server) handleContact(w http.ResponseWriter, r *http.Request) {
"content": content, "content": content,
"labels": labelsStr, "labels": labelsStr,
} }
html, _ := s.htmlGenerator.GenerateContactPage(false, "Failed to submit your message. Please try again later.", "", formData, nil, []generator.FeedItemInfo{}) html, _ := s.htmlGenerator.GenerateContactPage(false, "Failed to submit your message. Please try again later.", "", formData, nil, []generator.FeedItemInfo{}, nil)
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html)) w.Write([]byte(html))
return return
} }
// Success - render success page // Success - render success page
html, err := s.htmlGenerator.GenerateContactPage(true, "", eventID, nil, repoAnnouncement, []generator.FeedItemInfo{}) html, err := s.htmlGenerator.GenerateContactPage(true, "", eventID, nil, repoAnnouncement, []generator.FeedItemInfo{}, nil)
if err != nil { if err != nil {
http.Error(w, "Failed to generate success page", http.StatusInternalServerError) http.Error(w, "Failed to generate success page", http.StatusInternalServerError)
return return

6
internal/server/server.go

@ -26,6 +26,7 @@ type Server struct {
issueService IssueServiceInterface issueService IssueServiceInterface
repoAnnouncement string repoAnnouncement string
htmlGenerator HTMLGeneratorInterface htmlGenerator HTMLGeneratorInterface
nostrClient *nostr.Client
} }
// IssueServiceInterface defines the interface for issue service // IssueServiceInterface defines the interface for issue service
@ -37,12 +38,12 @@ type IssueServiceInterface interface {
// HTMLGeneratorInterface defines the interface for HTML generator // HTMLGeneratorInterface defines the interface for HTML generator
type HTMLGeneratorInterface interface { type HTMLGeneratorInterface interface {
GenerateContactPage(success bool, errorMsg string, eventID string, formData map[string]string, repoAnnouncement *nostr.RepoAnnouncement, feedItems []generator.FeedItemInfo) (string, error) GenerateContactPage(success bool, errorMsg string, eventID string, formData map[string]string, repoAnnouncement *nostr.RepoAnnouncement, feedItems []generator.FeedItemInfo, profile *nostr.Profile) (string, error)
GenerateErrorPage(statusCode int, feedItems []generator.FeedItemInfo) (string, error) GenerateErrorPage(statusCode int, feedItems []generator.FeedItemInfo) (string, error)
} }
// NewServer creates a new HTTP server // NewServer creates a new HTTP server
func NewServer(port int, pageCache *cache.Cache, feedCache *cache.FeedCache, issueService IssueServiceInterface, repoAnnouncement string, htmlGenerator HTMLGeneratorInterface) *Server { func NewServer(port int, pageCache *cache.Cache, feedCache *cache.FeedCache, issueService IssueServiceInterface, repoAnnouncement string, htmlGenerator HTMLGeneratorInterface, nostrClient *nostr.Client) *Server {
s := &Server{ s := &Server{
cache: pageCache, cache: pageCache,
feedCache: feedCache, feedCache: feedCache,
@ -50,6 +51,7 @@ func NewServer(port int, pageCache *cache.Cache, feedCache *cache.FeedCache, iss
issueService: issueService, issueService: issueService,
repoAnnouncement: repoAnnouncement, repoAnnouncement: repoAnnouncement,
htmlGenerator: htmlGenerator, htmlGenerator: htmlGenerator,
nostrClient: nostrClient,
} }
mux := http.NewServeMux() mux := http.NewServeMux()

3
static/GitCitadel_Icon_Black.svg

@ -0,0 +1,3 @@
<svg width="33" height="40" viewBox="0 0 33 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5891 35.4011L17.5106 39.9785C17.5092 39.979 17.5078 39.9796 17.5064 39.9802L17.5064 35.8771C17.5064 34.7972 17.9225 33.7587 18.6685 32.9767L22.5907 28.8654C23.8341 27.5621 24.5276 25.8312 24.5276 24.0314L24.5276 22.18C26.1639 21.6026 27.3361 20.0448 27.3361 18.2137C27.3361 15.8911 25.45 14.0082 23.1234 14.0082C20.7967 14.0082 18.9106 15.8911 18.9106 18.2137C18.9106 20.0448 20.0829 21.6026 21.7191 22.18L21.7191 24.0314C21.7191 25.1113 21.303 26.1498 20.557 26.9318L16.6348 31.0431C16.4441 31.2429 16.2664 31.4528 16.1021 31.6715C15.9378 31.4528 15.7601 31.2429 15.5695 31.0431L11.6472 26.9318C10.9012 26.1498 10.4851 25.1113 10.4851 24.0314L10.4851 22.18C12.1213 21.6026 13.2936 20.0448 13.2936 18.2137C13.2936 15.8911 11.4075 14.0082 9.08084 14.0082C6.7542 14.0082 4.86808 15.8911 4.86808 18.2137C4.86808 20.0448 6.04036 21.6026 7.67659 22.18L7.67659 24.0314C7.67659 25.8312 8.37011 27.5621 9.61349 28.8654L13.5357 32.9767C14.2817 33.7587 14.6979 34.7972 14.6979 35.8771L14.6979 40C14.6964 39.9995 14.695 39.9989 14.6936 39.9984L2.61761 35.4235C1.04655 34.8115 0 33.3065 0 31.5999L1.91217e-06 18.3826C2.77078e-06 8.56122 6.30147 2.98896 14.9546 0.182845C15.6983 -0.0609484 16.5035 -0.0609483 17.2496 0.182845C25.9052 2.98896 32.2042 8.54132 32.2042 18.3826L32.2042 31.5999C32.2018 33.294 31.1577 34.809 29.5866 35.4036L29.5891 35.4011Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

2
templates/base.html

@ -22,7 +22,7 @@
<meta name="twitter:image" content="{{.OGImage}}"> <meta name="twitter:image" content="{{.OGImage}}">
<link rel="canonical" href="{{.CanonicalURL}}"> <link rel="canonical" href="{{.CanonicalURL}}">
<link rel="icon" type="image/svg+xml" href="/static/GitCitadel_Icon_Gradient.svg"> <link rel="icon" type="image/svg+xml" href="/static/GitCitadel_Icon_Black.svg">
<link rel="stylesheet" href="/static/css/main.css"> <link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="/static/css/responsive.css"> <link rel="stylesheet" href="/static/css/responsive.css">
<link rel="stylesheet" href="/static/css/print.css" media="print"> <link rel="stylesheet" href="/static/css/print.css" media="print">

47
templates/contact.html

@ -1,6 +1,53 @@
{{define "content"}} {{define "content"}}
<article class="contact-page"> <article class="contact-page">
<h1>Contact Us</h1> <h1>Contact Us</h1>
<div class="contact-links" style="margin-bottom: 2rem; padding: 1rem; background: #f5f5f5; border-radius: 8px;">
<h2 style="margin-top: 0; font-size: 1.2rem;">Find us elsewhere:</h2>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="margin-bottom: 0.5rem;">
<a href="https://aitherboard.imwald.eu/repos/naddr1qvzqqqrhnypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqq9xw6t5vd5hgctyv4kqde47kt?tab=about" target="_blank" rel="noopener noreferrer" style="color: #0066cc; text-decoration: none;">
📦 Aitherboard Repository
</a>
</li>
<li style="margin-bottom: 0.5rem;">
<a href="https://github.com/ShadowySupercode" target="_blank" rel="noopener noreferrer" style="color: #0066cc; text-decoration: none;">
🐙 GitHub: ShadowySupercode
</a>
</li>
</ul>
</div>
{{if .Profile}}
<div class="nostr-profile" style="margin-bottom: 2rem; padding: 1rem; background: #f9f9f9; border-radius: 8px; border: 1px solid #ddd;">
<h2 style="margin-top: 0; font-size: 1.2rem;">Nostr Profile</h2>
<div style="display: flex; gap: 1rem; align-items: flex-start;">
{{if .Profile.Picture}}
<img src="{{.Profile.Picture}}" alt="Profile picture" style="width: 80px; height: 80px; border-radius: 50%; object-fit: cover;">
{{end}}
<div style="flex: 1;">
<h3 style="margin: 0 0 0.5rem 0; font-size: 1.1rem;">
{{if .Profile.DisplayName}}{{.Profile.DisplayName}}{{else if .Profile.Name}}{{.Profile.Name}}{{else}}Anonymous{{end}}
</h3>
{{if .Profile.Name}}
<p style="margin: 0 0 0.5rem 0; color: #666; font-size: 0.9rem;">@{{.Profile.Name}}</p>
{{end}}
{{if .Profile.About}}
<p style="margin: 0 0 0.5rem 0; white-space: pre-wrap;">{{.Profile.About}}</p>
{{end}}
{{if .Profile.Website}}
<p style="margin: 0.5rem 0 0 0;">
<a href="{{.Profile.Website}}" target="_blank" rel="noopener noreferrer" style="color: #0066cc; text-decoration: none;">🌐 Website</a>
</p>
{{end}}
{{if .Profile.NIP05}}
<p style="margin: 0.5rem 0 0 0; color: #666; font-size: 0.9rem;">NIP-05: {{.Profile.NIP05}}</p>
{{end}}
</div>
</div>
</div>
{{end}}
<p>Have a question, suggestion, or want to report an issue? Fill out the form below and we'll get back to you.</p> <p>Have a question, suggestion, or want to report an issue? Fill out the form below and we'll get back to you.</p>
{{if .Success}} {{if .Success}}

Loading…
Cancel
Save