package chat import ( "encoding/json" "fmt" "io" "log" "net/http" "strings" "time" "github.com/golang-jwt/jwt/v5" "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(messagingPipeline messaging.Continuation) 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() persist := messaging.PersistMessageContinuation(context.DBConn, context.User.Id, context.Id, now, true) var err error messaging.LogContinuation(messaging.Message{ FrenName: context.User.Name, Message: rawMessage, })(messagingPipeline, messaging.FailurePassingContinuation)(persist, messaging.FailurePassingContinuation)(messaging.IdContinuation, func(message messaging.Message) messaging.ContinuationChain { err = fmt.Errorf("err sending message from: %s %s", context.User, rawMessage) return messaging.FailurePassingContinuation(message) }) if err != nil { // yeah this might be a 400 or whatever, i’ll fix it later resp.WriteHeader(http.StatusInternalServerError) return failure(context, req, resp) } 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(allowedFrom string, signingKey string, messagingPipeline messaging.Continuation) 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) } if event.Data.Contact != allowedFrom { log.Printf("someone did something naughty %s", event.Data.Contact) return success(context, req, resp) } message, err := messaging.Decode(event.Data.Content) if err != nil { log.Printf("err when decoding message %s", err) resp.WriteHeader(http.StatusBadRequest) return failure(context, req, resp) } fren, err := database.FindFrenByName(context.DBConn, message.FrenName) if err != nil { log.Printf("err when getting fren %s %s", fren.Name, err) resp.WriteHeader(http.StatusBadRequest) return failure(context, req, resp) } persist := messaging.PersistMessageContinuation(context.DBConn, fren.Id, context.Id, event.Data.Timestamp, false) messaging.LogContinuation(*message)(messagingPipeline, messaging.FailurePassingContinuation)(persist, messaging.FailurePassingContinuation)(messaging.IdContinuation, func(message messaging.Message) messaging.ContinuationChain { err = fmt.Errorf("err propagating stuff for message %s", message) return messaging.FailurePassingContinuation(message) }) if err != nil { resp.WriteHeader(http.StatusInternalServerError) return failure(context, req, resp) } return success(context, req, resp) } } }