package api import ( "database/sql" "fmt" "log" "net/http" "os" "time" "git.simponic.xyz/simponic/phoneof/adapters/messaging" "git.simponic.xyz/simponic/phoneof/api/chat" "git.simponic.xyz/simponic/phoneof/api/template" "git.simponic.xyz/simponic/phoneof/api/types" "git.simponic.xyz/simponic/phoneof/args" "git.simponic.xyz/simponic/phoneof/utils" ) func LogRequestContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain { return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain { context.Start = time.Now().UTC() context.Id = utils.RandomId() log.Println(req.Method, req.URL.Path, req.RemoteAddr, context.Id) return success(context, req, resp) } } func LogExecutionTimeContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain { return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain { end := time.Now().UTC() log.Println(context.Id, "took", end.Sub(context.Start)) return success(context, req, resp) } } func HealthCheckContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain { return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain { resp.WriteHeader(200) resp.Write([]byte("healthy")) return success(context, req, resp) } } func FailurePassingContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain { return func(_success types.Continuation, failure types.Continuation) types.ContinuationChain { return failure(context, req, resp) } } func IdContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain { return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain { return success(context, req, resp) } } func CacheControlMiddleware(next http.Handler, maxAge int) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { header := fmt.Sprintf("public, max-age=%d", maxAge) w.Header().Set("Cache-Control", header) next.ServeHTTP(w, r) }) } func MakeMux(argv *args.Arguments, dbConn *sql.DB) *http.ServeMux { mux := http.NewServeMux() staticFileServer := http.FileServer(http.Dir(argv.StaticPath)) mux.Handle("GET /static/", http.StripPrefix("/static/", CacheControlMiddleware(staticFileServer, 3600))) makeRequestContext := func() *types.RequestContext { return &types.RequestContext{ DBConn: dbConn, Args: argv, TemplateData: &map[string]interface{}{}, } } mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(HealthCheckContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() templateFile := "home.html" LogRequestContinuation(requestContext, r, w)(template.TemplateContinuation(templateFile, true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) mux.HandleFunc("GET /chat", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(chat.ValidateFren, FailurePassingContinuation)(template.TemplateContinuation("chat.html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) mux.HandleFunc("GET /chat/messages", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(chat.ValidateFren, FailurePassingContinuation)(chat.FetchMessagesContinuation, FailurePassingContinuation)(template.TemplateContinuation("messages.html", false), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) messageHandler := messaging.HttpSmsMessagingAdapter{ ApiToken: os.Getenv("HTTPSMS_API_TOKEN"), FromPhoneNumber: os.Getenv("FROM_PHONE_NUMBER"), ToPhoneNumber: os.Getenv("TO_PHONE_NUMBER"), Endpoint: argv.HttpSmsEndpoint, } sendMessageContinuation := chat.SendMessageContinuation(&messageHandler) mux.HandleFunc("POST /chat", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(chat.ValidateFren, FailurePassingContinuation)(sendMessageContinuation, FailurePassingContinuation)(template.TemplateContinuation("chat.html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) smsEventProcessor := chat.ChatEventProcessorContinuation(os.Getenv("HTTPSMS_SIGNING_KEY")) mux.HandleFunc("POST /chat/event", func(w http.ResponseWriter, r *http.Request) { requestContext := makeRequestContext() LogRequestContinuation(requestContext, r, w)(smsEventProcessor, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation) }) return mux }