Use JSON for errors and page response

This commit is contained in:
Ian Gulliver
2024-11-26 13:26:26 -06:00
parent 89737edbd6
commit 0512fa41f6
7 changed files with 69 additions and 16 deletions

15
error.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"fmt"
"net/http"
)
type Error struct {
Message string `json:"message"`
}
func sendError(w http.ResponseWriter, code int, msg string, args ...any) {
w.WriteHeader(code)
sendJSON(w, Error{Message: fmt.Sprintf(msg, args...)})
}

6
go.mod
View File

@@ -2,11 +2,15 @@ module github.com/gopatchy/p
go 1.22 go 1.22
require github.com/openai/openai-go v0.1.0-alpha.38 require (
github.com/openai/openai-go v0.1.0-alpha.38
github.com/samber/lo v1.47.0
)
require ( require (
github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/text v0.16.0 // indirect
) )

4
go.sum
View File

@@ -1,5 +1,7 @@
github.com/openai/openai-go v0.1.0-alpha.38 h1:j/rL0aEIHWnWaPgA8/AXYKCI79ZoW44NTIpn7qfMEXQ= github.com/openai/openai-go v0.1.0-alpha.38 h1:j/rL0aEIHWnWaPgA8/AXYKCI79ZoW44NTIpn7qfMEXQ=
github.com/openai/openai-go v0.1.0-alpha.38/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A= github.com/openai/openai-go v0.1.0-alpha.38/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -10,3 +12,5 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=

14
json.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"encoding/json"
"net/http"
)
func sendJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
_ = enc.Encode(v)
}

23
main.go
View File

@@ -63,7 +63,7 @@ func (ph *PHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) { func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("invalid form: %s\n", err), http.StatusBadRequest) sendError(w, http.StatusBadRequest, "Parse form: %s", err)
return return
} }
@@ -73,7 +73,7 @@ func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) {
if m == "" { if m == "" {
err = ph.tmpl.Execute(w, ph.envs()) err = ph.tmpl.Execute(w, ph.envs())
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("execute %s: %s\n", ph.tmpl.Name(), err), http.StatusBadRequest) sendError(w, http.StatusInternalServerError, "Execute template %s: %s", ph.tmpl.Name(), err)
return return
} }
@@ -92,13 +92,13 @@ func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) {
}) })
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("failed to create PD request: %s\n", err), http.StatusBadRequest) sendError(w, http.StatusInternalServerError, "Create PD request: %s", err)
return return
} }
req, err := http.NewRequest("POST", "https://events.pagerduty.com/v2/enqueue", buf) req, err := http.NewRequest("POST", "https://events.pagerduty.com/v2/enqueue", buf)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("failed to create HTTP request: %s\n", err), http.StatusBadRequest) sendError(w, http.StatusInternalServerError, "Create HTTP request: %s", err)
return return
} }
@@ -106,7 +106,7 @@ func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) {
res, err := c.Do(req) res, err := c.Do(req)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("error from PD: %s\n", err), http.StatusBadRequest) sendError(w, http.StatusInternalServerError, "Call PagerDuty: %s", err)
return return
} }
@@ -114,17 +114,17 @@ func (ph *PHandler) serveRoot(w http.ResponseWriter, r *http.Request) {
_ = res.Body.Close() _ = res.Body.Close()
if res.StatusCode != 202 { if res.StatusCode != 202 {
http.Error(w, fmt.Sprintf("error from PD: %s", string(body)), http.StatusBadRequest) sendError(w, http.StatusInternalServerError, "PagerDuty error: %s", string(body))
return return
} }
_, _ = w.Write([]byte("page sent\n")) sendResponse(w, "Page sent")
} }
func (ph *PHandler) serveSuggest(w http.ResponseWriter, r *http.Request) { func (ph *PHandler) serveSuggest(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("invalid form: %s\n", err), http.StatusBadRequest) sendError(w, http.StatusBadRequest, "Parse form: %s", err)
return return
} }
@@ -132,21 +132,22 @@ func (ph *PHandler) serveSuggest(w http.ResponseWriter, r *http.Request) {
m := r.Form.Get("m") m := r.Form.Get("m")
if m == "" { if m == "" {
http.Error(w, "m param required", http.StatusBadRequest) sendError(w, http.StatusBadRequest, "m= param required")
return
} }
c := openai.NewClient() c := openai.NewClient()
comp, err := c.Chat.Completions.New(r.Context(), openai.ChatCompletionNewParams{ comp, err := c.Chat.Completions.New(r.Context(), openai.ChatCompletionNewParams{
Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ Messages: openai.F([]openai.ChatCompletionMessageParamUnion{
openai.SystemMessage(`You are an assistant helping users to write good text to include in an urgent page sent to a person. Good page text contains a very brief description of the problem (e.g. "down" or "slow"), the systems it affects (acronyms for system names are fine), the identity of the sender (first names are fine), and how to contact them (e.g. a phone number or incident Slack channel). The request will consist of just the user's proposed page text. Respond with just a very brief message suggesting improvements that the sender might make or saying "Looks good, send it!". Remember that the user is likely in an urgent, stressful situation, so make your response brief and err on the side of assuming that the message is sufficient if the text might be OK. Assume that the recipient already knows the message is urgent so the sender doesn't have to specify urgency.`), openai.SystemMessage(`You are an assistant helping users to write good text to include in an urgent page sent to a person. Good page text contains a very brief description of the problem (e.g. "down" or "slow"), the systems it affects (acronyms for system names are fine), the identity of the sender (first names are fine), and how to contact them (e.g. a phone number or incident Slack channel). The request will consist of just the user's proposed page text. Respond with just a very brief message suggesting improvements that the sender might make or saying "Looks good -- send it!". Remember that the user is likely in an urgent, stressful situation, so make your response brief and err on the side of assuming that the message is sufficient if the text might be OK. Assume that the recipient already knows the message is urgent so the sender doesn't have to specify urgency.`),
openai.UserMessage(m), openai.UserMessage(m),
}), }),
Model: openai.F(openai.ChatModelGPT4o), Model: openai.F(openai.ChatModelGPT4o),
}) })
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("error from openai: %s", err), http.StatusInternalServerError) sendError(w, http.StatusInternalServerError, "OpenAI error: %s", err)
return return
} }

14
response.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"net/http"
)
type Response struct {
Message string `json:"message"`
}
func sendResponse(w http.ResponseWriter, msg string, args ...any) {
sendJSON(w, Response{Message: fmt.Sprintf(msg, args...)})
}

View File

@@ -100,10 +100,11 @@ async function page() {
) )
if (!resp.ok) { if (!resp.ok) {
error('Failed to send page', await resp.text()); error('Failed to send page', (await resp.json()).message);
return; return;
} }
document.getElementById('sent-msg').innerText = (await resp.json()).message;
document.getElementById('sent').show(); document.getElementById('sent').show();
} }
@@ -112,7 +113,7 @@ async function suggestLater() {
clearTimeout(suggestTimer); clearTimeout(suggestTimer);
} }
suggestTimer = setTimeout(suggestNow, 3000); suggestTimer = setTimeout(suggestNow, 2000);
} }
async function suggestNow() { async function suggestNow() {
@@ -182,7 +183,7 @@ document.addEventListener('DOMContentLoaded', () => {
<br /> <br />
<sl-alert id="suggest" variant="primary"> <sl-alert id="suggest" variant="primary">
<sl-icon slot="icon" name="info-circle"></sl-icon> <sl-icon slot="icon" name="info-circle"></sl-icon>
<strong id="suggest-msg"></strong> <span id="suggest-msg"></span>
</sl-alert> </sl-alert>
<sl-alert id="err" variant="danger"> <sl-alert id="err" variant="danger">
<sl-icon slot="icon" name="exclamation-octagon"></sl-icon> <sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
@@ -191,7 +192,7 @@ document.addEventListener('DOMContentLoaded', () => {
</sl-alert> </sl-alert>
<sl-alert id="sent" variant="success"> <sl-alert id="sent" variant="success">
<sl-icon slot="icon" name="check2-circle"></sl-icon> <sl-icon slot="icon" name="check2-circle"></sl-icon>
<strong>Page sent</strong> <strong id="sent-msg"></strong>
</sl-alert> </sl-alert>
</sl-tab-panel> </sl-tab-panel>
<sl-tab-panel name="contact" style="text-align: center"> <sl-tab-panel name="contact" style="text-align: center">