phoneof/api/chat/chat.go

196 lines
5.9 KiB
Go
Raw Normal View History

2025-01-03 01:47:07 -08:00
package chat
import (
"encoding/json"
"github.com/golang-jwt/jwt/v5"
"io"
"log"
"net/http"
"strings"
"time"
"git.simponic.xyz/simponic/phoneof/adapters/messaging"
"git.simponic.xyz/simponic/phoneof/api/types"
"git.simponic.xyz/simponic/phoneof/database"
)
func ValidateFren(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
fren_id := req.FormValue("fren_id")
fren, err := database.FindFren(context.DBConn, fren_id)
if err != nil || fren == nil {
log.Printf("err fetching friend %s %s", fren, err)
resp.WriteHeader(http.StatusUnauthorized)
return failure(context, req, resp)
}
context.User = fren
(*context.TemplateData)["User"] = fren
return success(context, req, resp)
}
}
func FetchMessagesContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
before := time.Now().UTC()
var err error
if req.FormValue("before") != "" {
before, err = time.Parse(time.RFC3339, req.FormValue("before"))
if err != nil {
log.Printf("bad time: %s", err)
resp.WriteHeader(http.StatusBadRequest)
return failure(context, req, resp)
}
}
query := database.ListMessageQuery{
FrenId: context.User.Id,
Before: before,
Limit: 25,
}
messages, err := database.ListMessages(context.DBConn, query)
if err != nil {
log.Printf("err listing messages %v %s", query, err)
resp.WriteHeader(http.StatusInternalServerError)
return failure(context, req, resp)
}
(*context.TemplateData)["Messages"] = messages
return success(context, req, resp)
}
}
func SendMessageContinuation(messagingAdapter messaging.MessagingAdapter) func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
return func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
rawMessage := req.FormValue("message")
now := time.Now().UTC()
messageRequestId, err := messagingAdapter.SendMessage(context.User.Name + " " + rawMessage)
if err != nil {
log.Printf("err sending message %s %s %s", context.User, rawMessage, err)
// yeah this might be a 400 or whatever, ill fix it later
resp.WriteHeader(http.StatusInternalServerError)
return failure(context, req, resp)
}
message, err := database.SaveMessage(context.DBConn, &database.Message{
Id: messageRequestId,
FrenId: context.User.Id,
Message: rawMessage,
Time: now,
FrenSent: true,
})
log.Printf("Saved message %v", message)
return success(context, req, resp)
}
}
}
type Timestamp struct {
time.Time
}
func (p *Timestamp) UnmarshalJSON(bytes []byte) error {
var raw string
err := json.Unmarshal(bytes, &raw)
if err != nil {
log.Printf("error decoding timestamp: %s\n", err)
return err
}
p.Time, err = time.Parse(time.RFC3339, raw)
if err != nil {
log.Printf("error decoding timestamp: %s\n", err)
return err
}
return nil
}
type HttpSmsEventData struct {
Contact string `json:"contact"`
Content string `json:"content"`
Owner string `json:"owner"`
Timestamp time.Time `json:"timestamp"`
}
type HttpSmsEvent struct {
Data HttpSmsEventData `json:"data"`
Type string `json:"type"`
Id string `json:"id"`
}
func ChatEventProcessorContinuation(signingKey string) func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
return func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
// check signing
joken := strings.Split(req.Header.Get("Authorization"), "Bearer ")
_, err := jwt.Parse(joken[1], func(token *jwt.Token) (interface{}, error) {
return []byte(signingKey), nil
})
if err != nil {
log.Printf("invalid jwt %s", err)
resp.WriteHeader(http.StatusBadRequest)
return failure(context, req, resp)
}
// decode the event
defer req.Body.Close()
body, err := io.ReadAll(req.Body)
if err != nil {
log.Printf("err reading body")
resp.WriteHeader(http.StatusInternalServerError)
return failure(context, req, resp)
}
var event HttpSmsEvent
err = json.Unmarshal(body, &event)
if err != nil {
log.Printf("err unmarshaling body")
resp.WriteHeader(http.StatusBadRequest)
return failure(context, req, resp)
}
// we only care about received messages
if event.Type != "message.phone.received" {
log.Printf("got non-receive event %s", event.Type)
return success(context, req, resp)
}
// respond to texts with "<fren name> <content>"
content := strings.SplitN(event.Data.Content, " ", 2)
if len(content) < 2 {
log.Printf("no space delimiter")
resp.WriteHeader(http.StatusBadRequest)
return failure(context, req, resp)
}
name := content[0]
rawMessage := content[1]
fren, err := database.FindFrenByName(context.DBConn, name)
if err != nil {
log.Printf("err when getting fren %s %s", name, err)
resp.WriteHeader(http.StatusBadRequest)
return failure(context, req, resp)
}
// save the message!
_, err = database.SaveMessage(context.DBConn, &database.Message{
Id: event.Id,
FrenId: fren.Id,
Message: rawMessage,
Time: event.Data.Timestamp,
FrenSent: false,
})
if err != nil {
log.Printf("err when saving message %s %s", name, err)
resp.WriteHeader(http.StatusInternalServerError)
return failure(context, req, resp)
}
return success(context, req, resp)
}
}
}