@@ -15,6 +15,7 @@ import (
1515 "os"
1616 "os/exec"
1717 "regexp"
18+ "strconv"
1819 "strings"
1920 "sync"
2021 "time"
@@ -440,7 +441,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
440441 w .Header ().Set ("Access-Control-Allow-Origin" , origin )
441442 w .Header ().Set ("Vary" , "Origin" )
442443 }
443- w .Header ().Set ("Access-Control-Allow-Methods" , "POST, OPTIONS" )
444+ w .Header ().Set ("Access-Control-Allow-Methods" , "GET, POST, OPTIONS" )
444445 w .Header ().Set ("Access-Control-Allow-Headers" , "Content-Type, Authorization" )
445446
446447 // Handle preflight OPTIONS request.
@@ -452,19 +453,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
452453 // Route requests.
453454 switch {
454455 case r .URL .Path == "/v1/calculate" :
455- if r .Method != http .MethodPost {
456+ if r .Method != http .MethodPost && r . Method != http . MethodGet {
456457 http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
457458 return
458459 }
459460 s .handleCalculate (w , r )
460461 case r .URL .Path == "/v1/calculate/repo" :
461- if r .Method != http .MethodPost {
462+ if r .Method != http .MethodPost && r . Method != http . MethodGet {
462463 http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
463464 return
464465 }
465466 s .handleRepoSample (w , r )
466467 case r .URL .Path == "/v1/calculate/org" :
467- if r .Method != http .MethodPost {
468+ if r .Method != http .MethodPost && r . Method != http . MethodGet {
468469 http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
469470 return
470471 }
@@ -576,14 +577,39 @@ func (s *Server) handleCalculate(writer http.ResponseWriter, request *http.Reque
576577
577578// parseRequest parses and validates the incoming request.
578579func (s * Server ) parseRequest (ctx context.Context , r * http.Request ) (* CalculateRequest , error ) {
579- // SECURITY: Limit request body size to prevent memory exhaustion DoS.
580- const maxRequestSize = 1 << 20 // 1MB
581- r .Body = http .MaxBytesReader (nil , r .Body , maxRequestSize )
582-
583580 var req CalculateRequest
584- if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
585- s .logger .ErrorContext (ctx , "[parseRequest] Failed to decode JSON" , errorKey , sanitizeError (err ))
586- return nil , fmt .Errorf ("invalid JSON: %w" , err )
581+
582+ // Handle GET requests with query parameters
583+ if r .Method == http .MethodGet {
584+ req .URL = r .URL .Query ().Get ("url" )
585+
586+ // Parse optional config parameters
587+ if salaryStr := r .URL .Query ().Get ("salary" ); salaryStr != "" {
588+ if req .Config == nil {
589+ req .Config = & cost.Config {}
590+ }
591+ if salary , err := strconv .ParseFloat (salaryStr , 64 ); err == nil {
592+ req .Config .AnnualSalary = salary
593+ }
594+ }
595+ if benefitsStr := r .URL .Query ().Get ("benefits" ); benefitsStr != "" {
596+ if req .Config == nil {
597+ req .Config = & cost.Config {}
598+ }
599+ if benefits , err := strconv .ParseFloat (benefitsStr , 64 ); err == nil {
600+ req .Config .BenefitsMultiplier = benefits
601+ }
602+ }
603+ } else {
604+ // Handle POST requests with JSON body
605+ // SECURITY: Limit request body size to prevent memory exhaustion DoS.
606+ const maxRequestSize = 1 << 20 // 1MB
607+ r .Body = http .MaxBytesReader (nil , r .Body , maxRequestSize )
608+
609+ if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
610+ s .logger .ErrorContext (ctx , "[parseRequest] Failed to decode JSON" , errorKey , sanitizeError (err ))
611+ return nil , fmt .Errorf ("invalid JSON: %w" , err )
612+ }
587613 }
588614
589615 if req .URL == "" {
@@ -1098,13 +1124,49 @@ func (s *Server) handleOrgSample(writer http.ResponseWriter, request *http.Reque
10981124
10991125// parseRepoSampleRequest parses and validates repository sampling requests.
11001126func (s * Server ) parseRepoSampleRequest (ctx context.Context , r * http.Request ) (* RepoSampleRequest , error ) {
1101- const maxRequestSize = 1 << 20 // 1MB
1102- r .Body = http .MaxBytesReader (nil , r .Body , maxRequestSize )
1103-
11041127 var req RepoSampleRequest
1105- if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
1106- s .logger .ErrorContext (ctx , "[parseRepoSampleRequest] Failed to decode JSON" , errorKey , sanitizeError (err ))
1107- return nil , fmt .Errorf ("invalid JSON: %w" , err )
1128+
1129+ // Handle GET requests with query parameters
1130+ if r .Method == http .MethodGet {
1131+ req .Owner = r .URL .Query ().Get ("owner" )
1132+ req .Repo = r .URL .Query ().Get ("repo" )
1133+
1134+ // Parse optional parameters
1135+ if sampleStr := r .URL .Query ().Get ("sample" ); sampleStr != "" {
1136+ if sample , err := strconv .Atoi (sampleStr ); err == nil {
1137+ req .SampleSize = sample
1138+ }
1139+ }
1140+ if daysStr := r .URL .Query ().Get ("days" ); daysStr != "" {
1141+ if days , err := strconv .Atoi (daysStr ); err == nil {
1142+ req .Days = days
1143+ }
1144+ }
1145+ if salaryStr := r .URL .Query ().Get ("salary" ); salaryStr != "" {
1146+ if req .Config == nil {
1147+ req .Config = & cost.Config {}
1148+ }
1149+ if salary , err := strconv .ParseFloat (salaryStr , 64 ); err == nil {
1150+ req .Config .AnnualSalary = salary
1151+ }
1152+ }
1153+ if benefitsStr := r .URL .Query ().Get ("benefits" ); benefitsStr != "" {
1154+ if req .Config == nil {
1155+ req .Config = & cost.Config {}
1156+ }
1157+ if benefits , err := strconv .ParseFloat (benefitsStr , 64 ); err == nil {
1158+ req .Config .BenefitsMultiplier = benefits
1159+ }
1160+ }
1161+ } else {
1162+ // Handle POST requests with JSON body
1163+ const maxRequestSize = 1 << 20 // 1MB
1164+ r .Body = http .MaxBytesReader (nil , r .Body , maxRequestSize )
1165+
1166+ if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
1167+ s .logger .ErrorContext (ctx , "[parseRepoSampleRequest] Failed to decode JSON" , errorKey , sanitizeError (err ))
1168+ return nil , fmt .Errorf ("invalid JSON: %w" , err )
1169+ }
11081170 }
11091171
11101172 if req .Owner == "" {
@@ -1138,13 +1200,48 @@ func (s *Server) parseRepoSampleRequest(ctx context.Context, r *http.Request) (*
11381200
11391201// parseOrgSampleRequest parses and validates organization sampling requests.
11401202func (s * Server ) parseOrgSampleRequest (ctx context.Context , r * http.Request ) (* OrgSampleRequest , error ) {
1141- const maxRequestSize = 1 << 20 // 1MB
1142- r .Body = http .MaxBytesReader (nil , r .Body , maxRequestSize )
1143-
11441203 var req OrgSampleRequest
1145- if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
1146- s .logger .ErrorContext (ctx , "[parseOrgSampleRequest] Failed to decode JSON" , errorKey , sanitizeError (err ))
1147- return nil , fmt .Errorf ("invalid JSON: %w" , err )
1204+
1205+ // Handle GET requests with query parameters
1206+ if r .Method == http .MethodGet {
1207+ req .Org = r .URL .Query ().Get ("org" )
1208+
1209+ // Parse optional parameters
1210+ if sampleStr := r .URL .Query ().Get ("sample" ); sampleStr != "" {
1211+ if sample , err := strconv .Atoi (sampleStr ); err == nil {
1212+ req .SampleSize = sample
1213+ }
1214+ }
1215+ if daysStr := r .URL .Query ().Get ("days" ); daysStr != "" {
1216+ if days , err := strconv .Atoi (daysStr ); err == nil {
1217+ req .Days = days
1218+ }
1219+ }
1220+ if salaryStr := r .URL .Query ().Get ("salary" ); salaryStr != "" {
1221+ if req .Config == nil {
1222+ req .Config = & cost.Config {}
1223+ }
1224+ if salary , err := strconv .ParseFloat (salaryStr , 64 ); err == nil {
1225+ req .Config .AnnualSalary = salary
1226+ }
1227+ }
1228+ if benefitsStr := r .URL .Query ().Get ("benefits" ); benefitsStr != "" {
1229+ if req .Config == nil {
1230+ req .Config = & cost.Config {}
1231+ }
1232+ if benefits , err := strconv .ParseFloat (benefitsStr , 64 ); err == nil {
1233+ req .Config .BenefitsMultiplier = benefits
1234+ }
1235+ }
1236+ } else {
1237+ // Handle POST requests with JSON body
1238+ const maxRequestSize = 1 << 20 // 1MB
1239+ r .Body = http .MaxBytesReader (nil , r .Body , maxRequestSize )
1240+
1241+ if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
1242+ s .logger .ErrorContext (ctx , "[parseOrgSampleRequest] Failed to decode JSON" , errorKey , sanitizeError (err ))
1243+ return nil , fmt .Errorf ("invalid JSON: %w" , err )
1244+ }
11481245 }
11491246
11501247 if req .Org == "" {
0 commit comments