11package main
22
33import (
4+ "bytes"
45 "context"
56 "encoding/json"
67 "fmt"
78 "log/slog"
9+ "net/http"
10+ "net/http/httptest"
811 "strings"
912 "sync"
1013
1114 awsevents "github.com/aws/aws-lambda-go/events"
1215 "github.com/aws/aws-lambda-go/lambda"
16+ "github.com/cockroachdb/errors"
1317 "github.com/cruxstack/github-ops-app/internal/app"
1418 "github.com/cruxstack/github-ops-app/internal/config"
1519)
1620
1721var (
1822 initOnce sync.Once
1923 appInst * app.App
24+ router http.Handler
2025 logger * slog.Logger
2126 initErr error
2227)
@@ -27,14 +32,19 @@ func initApp() {
2732
2833 cfg , err := config .NewConfig ()
2934 if err != nil {
30- initErr = fmt . Errorf ( "config init failed: %w" , err )
35+ initErr = errors . Wrap ( err , "config init failed" )
3136 return
3237 }
33- appInst , initErr = app .New (context .Background (), cfg )
38+ appInst , initErr = app .NewApp (context .Background (), cfg , logger )
39+ if initErr != nil {
40+ return
41+ }
42+ router = appInst .Handler ()
3443 })
3544}
3645
37- // APIGatewayHandler converts API Gateway requests to unified app.Request.
46+ // APIGatewayHandler converts API Gateway requests to stdlib *http.Request
47+ // and routes them through the chi router.
3848func APIGatewayHandler (ctx context.Context , req awsevents.APIGatewayV2HTTPRequest ) (awsevents.APIGatewayV2HTTPResponse , error ) {
3949 initApp ()
4050 if initErr != nil {
@@ -50,29 +60,35 @@ func APIGatewayHandler(ctx context.Context, req awsevents.APIGatewayV2HTTPReques
5060 logger .Debug ("received api gateway request" , slog .String ("request" , string (j )))
5161 }
5262
53- headers := make (map [string ]string )
54- for key , value := range req .Headers {
55- headers [strings .ToLower (key )] = value
63+ httpReq , err := http .NewRequestWithContext (
64+ ctx ,
65+ req .RequestContext .HTTP .Method ,
66+ req .RawPath ,
67+ strings .NewReader (req .Body ),
68+ )
69+ if err != nil {
70+ return awsevents.APIGatewayV2HTTPResponse {
71+ StatusCode : 500 ,
72+ Body : "failed to construct http request" ,
73+ }, nil
5674 }
5775
58- appReq := app.Request {
59- Type : app .RequestTypeHTTP ,
60- Method : req .RequestContext .HTTP .Method ,
61- Path : req .RawPath ,
62- Headers : headers ,
63- Body : []byte (req .Body ),
76+ for key , value := range req .Headers {
77+ httpReq .Header .Set (key , value )
6478 }
6579
66- resp := appInst .HandleRequest (ctx , appReq )
80+ rec := httptest .NewRecorder ()
81+ router .ServeHTTP (rec , httpReq )
6782
6883 return awsevents.APIGatewayV2HTTPResponse {
69- StatusCode : resp . StatusCode ,
70- Headers : resp . Headers ,
71- Body : string ( resp .Body ),
84+ StatusCode : rec . Code ,
85+ Headers : flattenHeaders ( rec . Header ()) ,
86+ Body : rec .Body . String ( ),
7287 }, nil
7388}
7489
75- // EventBridgeHandler converts EventBridge events to unified app.Request.
90+ // EventBridgeHandler converts EventBridge events to POST /scheduled/{action}
91+ // requests and routes them through the chi router.
7692func EventBridgeHandler (ctx context.Context , evt awsevents.CloudWatchEvent ) error {
7793 initApp ()
7894 if initErr != nil {
@@ -90,16 +106,30 @@ func EventBridgeHandler(ctx context.Context, evt awsevents.CloudWatchEvent) erro
90106 return err
91107 }
92108
93- req := app.Request {
94- Type : app .RequestTypeScheduled ,
95- ScheduledAction : detail .Action ,
96- ScheduledData : detail .Data ,
109+ path := fmt .Sprintf ("%s/scheduled/%s" , appInst .Config .BasePath , detail .Action )
110+
111+ var body []byte
112+ if detail .Data != nil {
113+ body = detail .Data
97114 }
98115
99- resp := appInst .HandleRequest (ctx , req )
116+ httpReq , err := http .NewRequestWithContext (ctx , http .MethodPost , path , bytes .NewReader (body ))
117+ if err != nil {
118+ return errors .Wrap (err , "failed to construct http request" )
119+ }
100120
101- if resp .StatusCode >= 400 {
102- return fmt .Errorf ("scheduled event failed: %s" , string (resp .Body ))
121+ if appInst .Config .AdminToken != "" {
122+ httpReq .Header .Set ("Authorization" , "Bearer " + appInst .Config .AdminToken )
123+ }
124+ if len (body ) > 0 {
125+ httpReq .Header .Set ("Content-Type" , "application/json" )
126+ }
127+
128+ rec := httptest .NewRecorder ()
129+ router .ServeHTTP (rec , httpReq )
130+
131+ if rec .Code >= 400 {
132+ return errors .Newf ("scheduled event failed: %s" , rec .Body .String ())
103133 }
104134
105135 return nil
@@ -122,7 +152,18 @@ func UniversalHandler(ctx context.Context, event json.RawMessage) (any, error) {
122152 return nil , EventBridgeHandler (ctx , eventBridgeEvent )
123153 }
124154
125- return nil , fmt .Errorf ("unknown lambda event type" )
155+ return nil , errors .New ("unknown lambda event type" )
156+ }
157+
158+ // flattenHeaders converts multi-value http.Header to single-value map.
159+ func flattenHeaders (h http.Header ) map [string ]string {
160+ flat := make (map [string ]string , len (h ))
161+ for key , values := range h {
162+ if len (values ) > 0 {
163+ flat [key ] = values [0 ]
164+ }
165+ }
166+ return flat
126167}
127168
128169func main () {
0 commit comments