Skip to content

Commit 49a7cfa

Browse files
committed
tidy up stuff
1 parent 6118649 commit 49a7cfa

File tree

9 files changed

+319
-532
lines changed

9 files changed

+319
-532
lines changed
Lines changed: 60 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -1,231 +1,93 @@
11
package adapters
22

33
import (
4+
"cmp"
45
"encoding/json"
5-
"io"
6+
"fmt"
67
"net/http"
7-
"strings"
88

99
"github.com/go-chi/chi/v5"
1010
"github.com/hay-kot/hookfeed/backend/internal/data/dtos"
1111
)
1212

13-
type Adapter interface {
14-
UnmarshalRequest(r *http.Request) error
15-
AsFeedMessage() dtos.FeedMessageCreate
13+
// ntfyMessage represents a parsed ntfy-compatible message
14+
type ntfyMessage struct {
15+
Topic string `json:"topic"`
16+
Message string `json:"message"`
17+
Title string `json:"title"`
18+
Priority int32 `json:"priority"`
19+
Tags []string `json:"tags"`
20+
Click string `json:"click"`
21+
Icon string `json:"icon"`
22+
Actions []json.RawMessage `json:"actions"`
23+
Markdown bool `json:"markdown"`
1624
}
1725

18-
// NtfyMessage represents a parsed ntfy-compatible message
19-
type NtfyMessage struct {
20-
Topic string `json:"topic,omitempty"`
21-
Message string `json:"message,omitempty"`
22-
Title string `json:"title,omitempty"`
23-
Priority int32 `json:"priority,omitempty"`
24-
Tags []string `json:"tags,omitempty"`
25-
Click string `json:"click,omitempty"`
26-
Icon string `json:"icon,omitempty"`
27-
Actions []json.RawMessage `json:"actions,omitempty"`
28-
Markdown bool `json:"markdown,omitempty"`
29-
}
30-
31-
// NtfyAdapter adapts ntfy-style requests to FeedMessage
32-
type NtfyAdapter struct {
33-
Message NtfyMessage
34-
RawBody []byte
35-
RawHeaders map[string]string
36-
RawQueryParams map[string][]string
37-
}
38-
39-
// UnmarshalRequest parses an ntfy-compatible HTTP request
40-
func (na *NtfyAdapter) UnmarshalRequest(r *http.Request) error {
41-
topic := chi.URLParam(r, "topic")
42-
na.Message = NtfyMessage{
43-
Topic: topic,
44-
Priority: 3, // default priority
45-
}
46-
47-
// Read body once
48-
bodyBytes, err := io.ReadAll(r.Body)
26+
// ParseNtfyMessage parses a ntfy compatible http request and transforms it into
27+
// a validated creation object or returns an error.
28+
// Priority order: JSON body < Query Params < Headers
29+
func ParseNtfyMessage(r *http.Request) (dtos.FeedMessageCreate, error) {
30+
// Copy HTTP request data (raw body, headers, query params)
31+
data, err := feedMessageFromRequest(r)
4932
if err != nil {
50-
return err
33+
return dtos.FeedMessageCreate{}, fmt.Errorf("failed to copy request: %w", err)
5134
}
52-
na.RawBody = bodyBytes
53-
// Restore body for potential later use
54-
r.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
5535

56-
// Capture raw headers
57-
na.RawHeaders = make(map[string]string)
58-
for k, v := range r.Header {
59-
if len(v) > 0 {
60-
na.RawHeaders[k] = v[0]
61-
}
62-
}
36+
// Set the feed ID from URL parameter
37+
data.FeedID = chi.URLParam(r, "topic")
6338

64-
// Try to parse as JSON first
39+
// Parse JSON body if Content-Type is application/json
40+
var jsonMsg ntfyMessage
6541
if r.Header.Get("Content-Type") == "application/json" {
66-
var jsonMsg NtfyMessage
67-
if err := json.Unmarshal(bodyBytes, &jsonMsg); err == nil {
68-
// JSON parsed successfully
69-
if jsonMsg.Topic != "" {
70-
na.Message.Topic = jsonMsg.Topic
71-
}
72-
if jsonMsg.Message != "" {
73-
na.Message.Message = jsonMsg.Message
74-
}
75-
if jsonMsg.Title != "" {
76-
na.Message.Title = jsonMsg.Title
77-
}
78-
if jsonMsg.Priority > 0 {
79-
na.Message.Priority = jsonMsg.Priority
80-
}
81-
if len(jsonMsg.Tags) > 0 {
82-
na.Message.Tags = jsonMsg.Tags
83-
}
84-
na.Message.Click = jsonMsg.Click
85-
na.Message.Icon = jsonMsg.Icon
86-
na.Message.Actions = jsonMsg.Actions
87-
na.Message.Markdown = jsonMsg.Markdown
42+
var bodyData map[string]interface{}
43+
if err := json.Unmarshal(data.RawRequest, &bodyData); err == nil {
44+
// Try to unmarshal as ntfy message structure
45+
bodyBytes, _ := json.Marshal(bodyData)
46+
_ = json.Unmarshal(bodyBytes, &jsonMsg)
8847
}
8948
}
9049

91-
// Parse query parameters (override JSON if present)
50+
// Extract values from query parameters
9251
query := r.URL.Query()
93-
na.RawQueryParams = query
94-
95-
if title := GetQueryParam(query, "title", "t"); title != "" {
96-
na.Message.Title = title
97-
}
98-
99-
if msgText := GetQueryParam(query, "message", "m"); msgText != "" {
100-
na.Message.Message = msgText
101-
}
102-
103-
if priorityStr := GetQueryParam(query, "priority", "p"); priorityStr != "" {
104-
if p, err := ParsePriority(priorityStr); err == nil {
105-
na.Message.Priority = p
106-
}
107-
}
108-
109-
if tagsStr := GetQueryParam(query, "tags", "ta"); tagsStr != "" {
110-
na.Message.Tags = SplitAndTrim(tagsStr)
111-
}
112-
113-
if click := GetQueryParam(query, "click"); click != "" {
114-
na.Message.Click = click
115-
}
116-
117-
if icon := GetQueryParam(query, "icon"); icon != "" {
118-
na.Message.Icon = icon
119-
}
120-
121-
if markdown := GetQueryParam(query, "markdown", "md"); markdown != "" {
122-
na.Message.Markdown = ParseBool(markdown)
123-
}
124-
125-
// Parse headers (headers override query parameters if present)
126-
if title := GetHeader(r, "X-Title", "Title"); title != "" {
127-
na.Message.Title = title
128-
}
129-
130-
if msgText := GetHeader(r, "X-Message", "Message"); msgText != "" {
131-
na.Message.Message = msgText
132-
}
133-
134-
if priorityStr := GetHeader(r, "X-Priority", "Priority"); priorityStr != "" {
135-
if p, err := ParsePriority(priorityStr); err == nil {
136-
na.Message.Priority = p
137-
}
138-
}
139-
140-
if tagsStr := GetHeader(r, "X-Tags", "Tags"); tagsStr != "" {
141-
na.Message.Tags = SplitAndTrim(tagsStr)
142-
}
52+
queryTitle := GetQueryParam(query, "title", "t")
53+
queryMessage := GetQueryParam(query, "message", "m")
54+
queryPriorityStr := GetQueryParam(query, "priority", "p")
14355

144-
if click := GetHeader(r, "X-Click", "Click"); click != "" {
145-
na.Message.Click = click
56+
var queryPriority int32
57+
if queryPriorityStr != "" {
58+
queryPriority, _ = ParsePriority(queryPriorityStr)
14659
}
14760

148-
if icon := GetHeader(r, "X-Icon", "Icon"); icon != "" {
149-
na.Message.Icon = icon
150-
}
61+
// Extract values from headers
62+
headerTitle := GetHeader(r, "X-Title", "Title")
63+
headerMessage := GetHeader(r, "X-Message", "Message")
64+
headerPriorityStr := GetHeader(r, "X-Priority", "Priority")
15165

152-
if markdown := GetHeader(r, "X-Markdown", "Markdown"); markdown != "" {
153-
na.Message.Markdown = ParseBool(markdown)
66+
var headerPriority int32
67+
if headerPriorityStr != "" {
68+
headerPriority, _ = ParsePriority(headerPriorityStr)
15469
}
15570

156-
// If message is still empty, use body as plain text
157-
if na.Message.Message == "" {
158-
na.Message.Message = string(bodyBytes)
159-
}
160-
161-
return nil
162-
}
71+
// Use cmp.Or to select first non-empty value (precedence: headers > query > json)
72+
title := cmp.Or(headerTitle, queryTitle, jsonMsg.Title)
73+
message := cmp.Or(headerMessage, queryMessage, jsonMsg.Message)
74+
priority := cmp.Or(headerPriority, queryPriority, jsonMsg.Priority, int32(3))
16375

164-
// AsFeedMessage converts the ntfy message to a FeedMessageCreate DTO
165-
func (na *NtfyAdapter) AsFeedMessage() dtos.FeedMessageCreate {
166-
// Build the body structure for RawRequest
167-
var bodyData any
168-
if json.Valid(na.RawBody) {
169-
// Body is already valid JSON, unmarshal it
170-
json.Unmarshal(na.RawBody, &bodyData)
171-
} else {
172-
// Body is plain text, wrap it
173-
bodyData = map[string]string{"body": string(na.RawBody)}
174-
}
175-
176-
// Convert map[string]string headers to http.Header format
177-
headers := make(map[string][]string)
178-
for k, v := range na.RawHeaders {
179-
headers[k] = []string{v}
180-
}
181-
182-
// Use the constructor to ensure proper initialization of raw fields
183-
createDTO, err := dtos.NewFeedMessageCreateFromHTTP(
184-
na.Message.Topic,
185-
bodyData,
186-
headers,
187-
na.RawQueryParams,
188-
)
189-
if err != nil {
190-
// Fallback to empty DTO if constructor fails (shouldn't happen)
191-
createDTO = dtos.FeedMessageCreate{
192-
FeedID: na.Message.Topic,
193-
RawRequest: json.RawMessage("{}"),
194-
RawHeaders: json.RawMessage("{}"),
195-
RawQueryParams: json.RawMessage("{}"),
196-
Logs: []string{},
197-
Metadata: json.RawMessage("{}"),
76+
// If message is still empty, try to use the raw body as plain text
77+
if message == "" {
78+
var bodyData map[string]interface{}
79+
if err := json.Unmarshal(data.RawRequest, &bodyData); err == nil {
80+
// Check for $body key (plain text wrapped by copyBody)
81+
if bodyStr, ok := bodyData["$body"].(string); ok && bodyStr != "" {
82+
message = bodyStr
83+
}
19884
}
19985
}
20086

201-
// Build metadata from ntfy-specific fields
202-
metadata := make(map[string]interface{})
203-
if len(na.Message.Tags) > 0 {
204-
metadata["tags"] = na.Message.Tags
205-
}
206-
if na.Message.Click != "" {
207-
metadata["click"] = na.Message.Click
208-
}
209-
if na.Message.Icon != "" {
210-
metadata["icon"] = na.Message.Icon
211-
}
212-
if len(na.Message.Actions) > 0 {
213-
metadata["actions"] = na.Message.Actions
214-
}
215-
if na.Message.Markdown {
216-
metadata["markdown"] = true
217-
}
218-
metadataJSON, _ := json.Marshal(metadata)
219-
220-
// Override with ntfy-specific parsed fields
221-
title := na.Message.Title
222-
message := na.Message.Message
223-
priority := na.Message.Priority
224-
225-
createDTO.Title = &title
226-
createDTO.Message = &message
227-
createDTO.Priority = &priority
228-
createDTO.Metadata = json.RawMessage(metadataJSON)
87+
// Set the parsed ntfy fields
88+
data.Title = title
89+
data.Message = message
90+
data.Priority = priority
22991

230-
return createDTO
92+
return data, nil
23193
}

backend/internal/services/adapters/ntfy_2.go

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)