feat: add server side of things
						commit
						2783ec1d0b
					
				@ -0,0 +1 @@
 | 
			
		||||
coverage.db
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
FROM golang:1.16 AS build
 | 
			
		||||
WORKDIR /root/
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN go build -o coverage .
 | 
			
		||||
 | 
			
		||||
FROM golang:1.16
 | 
			
		||||
WORKDIR /root/
 | 
			
		||||
COPY --from=build /root/coverage .
 | 
			
		||||
EXPOSE 8080
 | 
			
		||||
CMD ["./coverage"]
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
 | 
			
		||||
	"gitea.teamortix.com/team-ortix/coverage/db"
 | 
			
		||||
	"gitea.teamortix.com/team-ortix/coverage/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	port   = os.Getenv("COVERAGE_PORT")
 | 
			
		||||
	dbFile = os.Getenv("DB_FILE")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	if port == "" {
 | 
			
		||||
		port = "8080"
 | 
			
		||||
	}
 | 
			
		||||
	if dbFile == "" {
 | 
			
		||||
		dbFile = "coverage.db"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c := make(chan os.Signal, 1)
 | 
			
		||||
	signal.Notify(c, os.Interrupt)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		db.OpenDatabase(dbFile)
 | 
			
		||||
		server.StartServer(port)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	log.Printf("server starting. at http://0.0.0.0:%s", port)
 | 
			
		||||
 | 
			
		||||
	<-c
 | 
			
		||||
	fmt.Println("\nAborting...")
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	// Required to connect to database.
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var db *sql.DB
 | 
			
		||||
 | 
			
		||||
func OpenDatabase(fileLocation string) {
 | 
			
		||||
	var err error
 | 
			
		||||
	db, err = sql.Open("sqlite3", fileLocation)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("could not open database: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Initializes the table.
 | 
			
		||||
	_, err = db.Exec(`CREATE TABLE IF NOT EXISTS badge (
 | 
			
		||||
		namespace VARCHAR(64) NOT NULL,
 | 
			
		||||
		project_name VARCHAR(64) NOT NULL,
 | 
			
		||||
		coverage INTEGER NOT NULL,
 | 
			
		||||
		html TEXT NOT NULL,
 | 
			
		||||
 | 
			
		||||
		branch VARCHAR(64),
 | 
			
		||||
		pull INTEGER
 | 
			
		||||
	)`)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Could not create table: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var ErrNoData = errors.New("no badge found with the provided data")
 | 
			
		||||
 | 
			
		||||
type CoverageData struct {
 | 
			
		||||
	Namespace string
 | 
			
		||||
	Project   string
 | 
			
		||||
	Coverage  float64
 | 
			
		||||
	HTML      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// QueryByBranch returns CoverageData for the provided branch for the project.
 | 
			
		||||
// If there is no such data, it returns ErrNoData.
 | 
			
		||||
func QueryByBranch(namespace, project, branch string) (CoverageData, error) {
 | 
			
		||||
	result, err := db.Query(`SELECT coverage,html FROM badge WHERE 
 | 
			
		||||
		namespace=? AND
 | 
			
		||||
		project_name=? AND
 | 
			
		||||
		branch=?`,
 | 
			
		||||
		namespace, project, branch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CoverageData{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parseRows(result, namespace, project)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// QueryByBranch returns CoverageData for the provided pull request of the project.
 | 
			
		||||
// If there is no such data, it returns ErrNoData.
 | 
			
		||||
func QueryByPull(namespace, project, pullStr string) (CoverageData, error) {
 | 
			
		||||
	pull, err := strconv.Atoi(pullStr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CoverageData{}, ErrNoData
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := db.Query(`SELECT coverage, html FROM badge WHERE 
 | 
			
		||||
		namespace=? AND
 | 
			
		||||
		project_name=? AND
 | 
			
		||||
		pull=?`,
 | 
			
		||||
		namespace, project, pull)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CoverageData{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parseRows(result, namespace, project)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseRows(result *sql.Rows, namespace, project string) (CoverageData, error) {
 | 
			
		||||
	if !result.Next() {
 | 
			
		||||
		return CoverageData{}, ErrNoData
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var coverage float64
 | 
			
		||||
	var html string
 | 
			
		||||
	err := result.Scan(&coverage, &html)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CoverageData{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = result.Close(); err != nil {
 | 
			
		||||
		return CoverageData{}, ErrNoData
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return CoverageData{
 | 
			
		||||
		Namespace: namespace,
 | 
			
		||||
		Project:   project,
 | 
			
		||||
		Coverage:  coverage,
 | 
			
		||||
		HTML:      html,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func UploadBranchData(namespace, project, branch, coverageStr, html string) error {
 | 
			
		||||
	coverage, err := strconv.ParseFloat(coverageStr, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("coverage param must be valid float, instead got %s", coverageStr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Because we don't have have a primary key, we must do the upsert manually.
 | 
			
		||||
	_, err = QueryByBranch(namespace, project, branch)
 | 
			
		||||
	if err == ErrNoData {
 | 
			
		||||
		_, err = db.Exec("INSERT INTO badge (namespace, project_name, branch, coverage, html) VALUES (?, ?, ?, ?, ?)",
 | 
			
		||||
			namespace, project, branch, coverage, html)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("could not update database: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = db.Exec("UPDATE badge SET coverage=?, html=? WHERE namespace=? AND project_name=? AND branch=?",
 | 
			
		||||
		coverage, html, namespace, project, branch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("could not update database: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UploadPullData(namespace, project, pullStr, coverageStr, html string) error {
 | 
			
		||||
	pull, err := strconv.Atoi(pullStr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("pull param must be valid int, instead got %s", pullStr)
 | 
			
		||||
	}
 | 
			
		||||
	coverage, err := strconv.ParseFloat(coverageStr, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("coverage param must be valid float, instead got %s", coverageStr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Because we don't have have a primary key, we must do the upsert manually.
 | 
			
		||||
	_, err = QueryByPull(namespace, project, pullStr)
 | 
			
		||||
	if err == ErrNoData {
 | 
			
		||||
		_, err = db.Exec("INSERT INTO badge (namespace, project_name, pull, coverage, html) VALUES (?, ?, ?, ?, ?)",
 | 
			
		||||
			namespace, project, pull, coverage, html)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("could not update database: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = db.Exec("UPDATE badge SET coverage=?, html=? WHERE namespace=? AND project_name=? AND pull=?",
 | 
			
		||||
		coverage, html, namespace, project, pull)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("could not update database: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
module gitea.teamortix.com/team-ortix/coverage
 | 
			
		||||
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/gorilla/mux v1.8.0
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.6
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 | 
			
		||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
package pill
 | 
			
		||||
@ -0,0 +1,79 @@
 | 
			
		||||
package pill
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	// Used for embedding pill.
 | 
			
		||||
	_ "embed"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"text/template"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	//go:embed pill.svg
 | 
			
		||||
	pill string
 | 
			
		||||
 | 
			
		||||
	tmpl = template.New("svgTemplate")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	modifier = 0.2
 | 
			
		||||
	max      = 190
 | 
			
		||||
	blue     = 55
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Pill is used to execute the pill.svg template.
 | 
			
		||||
// Width is dependent on whether the percentage is 2 digits or 3 digits.
 | 
			
		||||
type Pill struct {
 | 
			
		||||
	Percentage string
 | 
			
		||||
	Colour     string
 | 
			
		||||
	Width      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPill creates creates a Pill, automatically calculating the colour, width, and formatting the percentage.
 | 
			
		||||
// Note: %02.0f gives us a float with 0 decimals and 0 padding for 2 numbers less than 10.
 | 
			
		||||
func NewPill(percentDecimal float64) Pill {
 | 
			
		||||
	square := math.Pow(percentDecimal, 2)
 | 
			
		||||
	red := modifier + clamp(2-2*square, 0, 1)*(1-modifier)
 | 
			
		||||
	green := modifier + clamp(2*square, 0, 1)*(1-modifier)
 | 
			
		||||
 | 
			
		||||
	percentage := fmt.Sprintf("%.0f", percentDecimal*100)
 | 
			
		||||
	colour := fmt.Sprintf("rgb(%.0f, %.0f, %d)", red*max, green*max, blue)
 | 
			
		||||
	width := "250"
 | 
			
		||||
	if len(percentage) == 1 {
 | 
			
		||||
		percentage = " " + percentage
 | 
			
		||||
		width = "220"
 | 
			
		||||
	}
 | 
			
		||||
	if len(percentage) == 3 {
 | 
			
		||||
		width = "300"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return Pill{
 | 
			
		||||
		Percentage: percentage,
 | 
			
		||||
		Colour:     colour,
 | 
			
		||||
		Width:      width,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute runs the pill template with the data and writes the result to the writer given.
 | 
			
		||||
func (p Pill) Execute(w io.Writer) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	tmpl, err = tmpl.Parse(pill)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = tmpl.Execute(w, p)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func clamp(n, min, max float64) float64 {
 | 
			
		||||
	switch {
 | 
			
		||||
	case n > max:
 | 
			
		||||
		return max
 | 
			
		||||
	case n < min:
 | 
			
		||||
		return min
 | 
			
		||||
	default:
 | 
			
		||||
		return n
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,11 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="96" height="20" role="img" aria-label="coverage: {{.Percentage}}%">
 | 
			
		||||
    <title>coverage: {{.Percentage}}%</title>
 | 
			
		||||
    <g shape-rendering="crispEdges">
 | 
			
		||||
        <rect width="61" height="20" fill="#555"/>
 | 
			
		||||
        <rect x="61" width="35" height="20" fill="{{.Colour}}"/>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
 | 
			
		||||
        <text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text>
 | 
			
		||||
        <text x="780" y="140" transform="scale(.1)" fill="#fff" textLength="{{.Width}}">{{.Percentage}}%</text>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 735 B  | 
@ -0,0 +1,116 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"gitea.teamortix.com/team-ortix/coverage/db"
 | 
			
		||||
	"gitea.teamortix.com/team-ortix/coverage/pill"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type endpointData struct {
 | 
			
		||||
	thirdParam string
 | 
			
		||||
	queryFunc  func(string, string, string) (db.CoverageData, error)
 | 
			
		||||
	handle     func(db.CoverageData)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d endpointData) run(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	params, ok := getAllParams(w, r, d.thirdParam)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := d.queryFunc(params[0], params[1], params[2])
 | 
			
		||||
	if err == db.ErrNoData {
 | 
			
		||||
		http.Error(w, "not found", http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("could not query from db: %v", err), http.StatusInternalServerError)
 | 
			
		||||
		log.Printf("error occurred while querying data from db: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.handle(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleBranchReport(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	endpointData{
 | 
			
		||||
		thirdParam: "branch",
 | 
			
		||||
		queryFunc:  db.QueryByBranch,
 | 
			
		||||
		handle: func(cd db.CoverageData) {
 | 
			
		||||
			w.Header().Set("Content-Type", "text/html")
 | 
			
		||||
			fmt.Fprint(w, cd.HTML)
 | 
			
		||||
		},
 | 
			
		||||
	}.run(w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleBadgeBranch(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	endpointData{
 | 
			
		||||
		thirdParam: "branch",
 | 
			
		||||
		queryFunc:  db.QueryByBranch,
 | 
			
		||||
		handle: func(cd db.CoverageData) {
 | 
			
		||||
			p := pill.NewPill(cd.Coverage)
 | 
			
		||||
 | 
			
		||||
			w.Header().Set("Content-Type", "image/svg+xml")
 | 
			
		||||
			err := p.Execute(w)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				http.Error(w, fmt.Sprintf("could not write pill: %v", err), http.StatusInternalServerError)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
	}.run(w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handlePullReport(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	endpointData{
 | 
			
		||||
		thirdParam: "pull",
 | 
			
		||||
		queryFunc:  db.QueryByPull,
 | 
			
		||||
		handle: func(cd db.CoverageData) {
 | 
			
		||||
			w.Header().Set("Content-Type", "text/html")
 | 
			
		||||
			fmt.Fprint(w, cd.HTML)
 | 
			
		||||
		},
 | 
			
		||||
	}.run(w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handlePullBadge(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	endpointData{
 | 
			
		||||
		thirdParam: "pull",
 | 
			
		||||
		queryFunc:  db.QueryByPull,
 | 
			
		||||
		handle: func(cd db.CoverageData) {
 | 
			
		||||
			p := pill.NewPill(cd.Coverage)
 | 
			
		||||
 | 
			
		||||
			w.Header().Set("Content-Type", "image/svg+xml")
 | 
			
		||||
			err := p.Execute(w)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				http.Error(w, fmt.Sprintf("could not write pill: %v", err), http.StatusInternalServerError)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
	}.run(w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAllParams(w http.ResponseWriter, r *http.Request, thirdParam string) ([3]string, bool) {
 | 
			
		||||
	vars := mux.Vars(r)
 | 
			
		||||
	namespace, ok := vars["namespace"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, "Path 'namespace' not found", http.StatusBadRequest)
 | 
			
		||||
		return [3]string{}, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	project, ok := vars["project"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, "Path 'project' not found", http.StatusBadRequest)
 | 
			
		||||
		return [3]string{}, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	third, ok := vars[thirdParam]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("Path '%s' not found", thirdParam), http.StatusBadRequest)
 | 
			
		||||
		return [3]string{}, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return [3]string{namespace, project, third}, true
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"gitea.teamortix.com/team-ortix/coverage/pill"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func StartServer(port string) {
 | 
			
		||||
	r := mux.NewRouter()
 | 
			
		||||
	r.HandleFunc("/badge/{percentage}", handleBadge)
 | 
			
		||||
 | 
			
		||||
	r.HandleFunc("/report/branch/{namespace}/{project}/{branch}", handleBranchReport)
 | 
			
		||||
	r.HandleFunc("/report/pulls/{namespace}/{project}/{pull}", handlePullReport)
 | 
			
		||||
	r.HandleFunc("/badge/branch/{namespace}/{project}/{branch}", handleBadgeBranch)
 | 
			
		||||
	r.HandleFunc("/badge/pulls/{namespace}/{project}/{pull}", handlePullBadge)
 | 
			
		||||
 | 
			
		||||
	r.HandleFunc("/upload/branch/{namespace}/{project}/{branch}", uploadBranch)
 | 
			
		||||
	r.HandleFunc("/upload/pulls/{namespace}/{project}/{branch}", uploadPull)
 | 
			
		||||
 | 
			
		||||
	portStr := fmt.Sprintf(":%s", port)
 | 
			
		||||
	err := http.ListenAndServe(portStr, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("could not start server: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleBadge(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	percentString, ok := mux.Vars(r)["percentage"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, "could not find percentage in path", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	percent, err := strconv.ParseFloat(percentString, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("could not convert %s to a number", percentString), http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p := pill.NewPill(percent / 100)
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "image/svg+xml")
 | 
			
		||||
	err = p.Execute(w)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("could not write pill: %v", err), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"gitea.teamortix.com/team-ortix/coverage/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const coverageSecret = "COVERAGE_SECRET"
 | 
			
		||||
 | 
			
		||||
var secretEnv = os.Getenv(coverageSecret)
 | 
			
		||||
 | 
			
		||||
func uploadBranch(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	upload(w, r, "branch", db.UploadBranchData)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func uploadPull(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	upload(w, r, "pull", db.UploadPullData)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func upload(w http.ResponseWriter, r *http.Request,
 | 
			
		||||
	uploadType string, uploadFunc func(string, string, string, string, string) error) {
 | 
			
		||||
	if r.Method != "POST" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := r.ParseMultipartForm(5 << 20)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, "file too large", http.StatusRequestEntityTooLarge)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = r.ParseForm()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, "could not parse form", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	secret := r.Form.Get("secret")
 | 
			
		||||
	namespace := r.Form.Get("namespace")
 | 
			
		||||
	project := r.Form.Get("project")
 | 
			
		||||
	uploadValue := r.Form.Get(uploadType)
 | 
			
		||||
	coverage := r.Form.Get("coverage")
 | 
			
		||||
 | 
			
		||||
	if secretEnv != secret && secretEnv != "" {
 | 
			
		||||
		http.Error(w, "invalid secret provided", http.StatusUnauthorized)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if namespace == "" || project == "" || uploadValue == "" || coverage == "" {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("request params [namespace project, %s, coverage] must all be present", uploadType),
 | 
			
		||||
			http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	report, _, err := r.FormFile("report")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, "could not parse report parameter", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	bytes, err := io.ReadAll(report)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("could not read file: %v", err), http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = uploadFunc(namespace, project, uploadValue, coverage, string(bytes))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("could not upload data to database: %v", err), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintln(w, "uploaded")
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue