[First Commit]
This commit is contained in:
commit
23b80bc084
18 changed files with 506 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
*.db
|
||||||
|
.idea
|
||||||
|
build/
|
||||||
|
|
||||||
|
templates/*.go
|
5
Makefile
Normal file
5
Makefile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
all:
|
||||||
|
templ generate
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
go build -o build/sonarqube-badges .
|
23
config/config.go
Normal file
23
config/config.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/kelseyhightower/envconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
SqHost string `envconfig:"SQ_HOST"`
|
||||||
|
AppPassword string `envconfig:"APP_PASSWORD"`
|
||||||
|
Secret string `envconfig:"SECRET"`
|
||||||
|
DataPath string `envconfig:"DATA_PATH"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessConfiguration(ctx context.Context) context.Context {
|
||||||
|
var cfg Config
|
||||||
|
err := envconfig.Process("", &cfg)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to process config")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.WithValue(ctx, "config", cfg)
|
||||||
|
}
|
23
go.mod
Normal file
23
go.mod
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
module sonarqube-badge
|
||||||
|
|
||||||
|
go 1.24
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/a-h/templ v0.3.833
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
github.com/gorilla/handlers v1.5.2
|
||||||
|
github.com/gorilla/mux v1.8.1
|
||||||
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
|
github.com/syronz/goAES v0.4.0
|
||||||
|
gorm.io/driver/sqlite v1.5.7
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
|
)
|
30
go.sum
Normal file
30
go.sum
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
|
||||||
|
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||||
|
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||||
|
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/syronz/goAES v0.4.0 h1:amXL8f0fSx1I+k2EPebq19hMkXSdcmeL1bqJzFzJZp4=
|
||||||
|
github.com/syronz/goAES v0.4.0/go.mod h1:5bXPMfiPVfXQWqZ5UFuPlQW1Lkx4fv3dJ8IoBZPXQgo=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
|
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
16
main.go
Normal file
16
main.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sonarqube-badge/config"
|
||||||
|
"sonarqube-badge/router"
|
||||||
|
"sonarqube-badge/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = config.ProcessConfiguration(ctx)
|
||||||
|
ctx = store.CreateDatabase(ctx)
|
||||||
|
|
||||||
|
router.StartServer(ctx)
|
||||||
|
}
|
39
router/api/login.go
Normal file
39
router/api/login.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"sonarqube-badge/security"
|
||||||
|
)
|
||||||
|
|
||||||
|
func postLogin(res http.ResponseWriter, req *http.Request) {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
http.Error(res, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
email := req.Form.Get("email")
|
||||||
|
password := req.Form.Get("password")
|
||||||
|
|
||||||
|
if email == "1@example.com" && password == "hello" {
|
||||||
|
token, err := security.CreateToken(email)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "jwt-token",
|
||||||
|
Value: token,
|
||||||
|
Path: "/",
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
}
|
||||||
|
http.SetCookie(res, &cookie)
|
||||||
|
} else {
|
||||||
|
res.WriteHeader(http.StatusUnauthorized)
|
||||||
|
res.Write([]byte("Wrong email or password"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoginRouter(r *mux.Router) {
|
||||||
|
r.HandleFunc("", postLogin).Methods("POST")
|
||||||
|
}
|
99
router/api/projectBadge.go
Normal file
99
router/api/projectBadge.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sonarqube-badge/config"
|
||||||
|
"sonarqube-badge/router/middlewares"
|
||||||
|
"sonarqube-badge/security"
|
||||||
|
"sonarqube-badge/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type setProjectBadgePayload struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProjectBadge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
projectName := vars["name"]
|
||||||
|
metric := vars["metric"]
|
||||||
|
|
||||||
|
print(projectName)
|
||||||
|
|
||||||
|
project := store.SQProject{}
|
||||||
|
ctx := r.Context()
|
||||||
|
cfg := ctx.Value("config").(config.Config)
|
||||||
|
ctx.Value("db").(*gorm.DB).First(&project, "project_name = ?", projectName)
|
||||||
|
if project.ID == 0 {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte("project not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := security.DecryptAES(cfg.Secret, project.Token)
|
||||||
|
fmt.Printf("%s", token)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/api/project_badges/measure?metric=%s&project=%s&token=%s", cfg.SqHost, metric, projectName, token)
|
||||||
|
request, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
os.Stderr.Write([]byte(err.Error()))
|
||||||
|
w.Write([]byte("internal server error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := io.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
os.Stderr.Write([]byte(err.Error()))
|
||||||
|
w.Write([]byte("internal server error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "image/svg+xml")
|
||||||
|
w.WriteHeader(request.StatusCode)
|
||||||
|
w.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setProjectBadge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var payload setProjectBadgePayload
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
projectName := vars["name"]
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&payload)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
os.Stderr.Write([]byte(err.Error()))
|
||||||
|
w.Write([]byte("invalid payload"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
db := ctx.Value("db").(*gorm.DB)
|
||||||
|
|
||||||
|
if !middlewares.CheckAuth(ctx, r) {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
project := store.SQProject{}
|
||||||
|
|
||||||
|
db.First(&project, "project_name = ?", projectName)
|
||||||
|
|
||||||
|
project.ProjectName = projectName
|
||||||
|
project.Token = security.EncryptAES(ctx.Value("config").(config.Config).Secret, payload.Token)
|
||||||
|
|
||||||
|
db.Save(&project)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("Successfully added"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProjectBadgeRouter(r *mux.Router) {
|
||||||
|
r.HandleFunc("/{name}/{metric}", getProjectBadge).Methods("GET")
|
||||||
|
r.HandleFunc("/{name}", setProjectBadge).Methods("POST")
|
||||||
|
}
|
43
router/middlewares/checkAuth.go
Normal file
43
router/middlewares/checkAuth.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sonarqube-badge/config"
|
||||||
|
"sonarqube-badge/security"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckAuth(ctx context.Context, r *http.Request) bool {
|
||||||
|
config := ctx.Value("config").(config.Config)
|
||||||
|
if r.Header.Get("Authorization") == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
token := r.Header.Get("Authorization")
|
||||||
|
if strings.HasPrefix(token, "Bearer ") {
|
||||||
|
token = token[7:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return token == config.AppPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckJwtToken(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tokenCookie, err := r.Cookie("jwt-token")
|
||||||
|
if err != nil || tokenCookie == nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = security.VerifyToken(tokenCookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
37
router/server.go
Normal file
37
router/server.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sonarqube-badge/router/api"
|
||||||
|
"sonarqube-badge/router/views"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartServer(ctx context.Context) {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// API ROUTES
|
||||||
|
api.ProjectBadgeRouter(r.PathPrefix("/api/project/badge").Subrouter())
|
||||||
|
api.LoginRouter(r.PathPrefix("/api/login").Subrouter())
|
||||||
|
|
||||||
|
// VIEW ROUTES
|
||||||
|
views.IndexRouter(r)
|
||||||
|
views.LoginRouter(r)
|
||||||
|
|
||||||
|
credentials := handlers.AllowCredentials()
|
||||||
|
methods := handlers.AllowedMethods([]string{"POST", "GET", "OPTIONS"})
|
||||||
|
ttl := handlers.MaxAge(3600)
|
||||||
|
|
||||||
|
server := http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
BaseContext: func(listener net.Listener) context.Context {
|
||||||
|
return ctx
|
||||||
|
},
|
||||||
|
Handler: handlers.CORS(credentials, methods, ttl)(r),
|
||||||
|
}
|
||||||
|
|
||||||
|
server.ListenAndServe()
|
||||||
|
}
|
18
router/views/index.go
Normal file
18
router/views/index.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"sonarqube-badge/router/middlewares"
|
||||||
|
"sonarqube-badge/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getIndex(res http.ResponseWriter, req *http.Request) {
|
||||||
|
templates.Layout(templates.Index(), "Index").Render(req.Context(), res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IndexRouter(r *mux.Router) {
|
||||||
|
subrouter := r.PathPrefix("").Subrouter()
|
||||||
|
subrouter.Use(middlewares.CheckJwtToken)
|
||||||
|
subrouter.HandleFunc("/", getIndex).Methods("GET")
|
||||||
|
}
|
15
router/views/login.go
Normal file
15
router/views/login.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"sonarqube-badge/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLogin(res http.ResponseWriter, req *http.Request) {
|
||||||
|
templates.Layout(templates.Login(), "Login").Render(req.Context(), res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoginRouter(r *mux.Router) {
|
||||||
|
r.HandleFunc("/login", getLogin).Methods("GET")
|
||||||
|
}
|
18
security/aes.go
Normal file
18
security/aes.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package security
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
goaes "github.com/syronz/goAES"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EncryptAES(password string, plaintext string) string {
|
||||||
|
iv := sha256.Sum256([]byte(password))
|
||||||
|
aes, _ := goaes.New().Key(password).IV(string(iv[:16])).Build()
|
||||||
|
return aes.Encrypt(plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecryptAES(password string, ct string) string {
|
||||||
|
iv := sha256.Sum256([]byte(password))
|
||||||
|
aes, _ := goaes.New().Key(password).IV(string(iv[:16])).Build()
|
||||||
|
return aes.Decrypt(ct)
|
||||||
|
}
|
37
security/jwt.go
Normal file
37
security/jwt.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package security
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JwtClaims struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateToken(username string) (string, error) {
|
||||||
|
t := jwt.NewWithClaims(jwt.SigningMethodHS256,
|
||||||
|
jwt.MapClaims{
|
||||||
|
"username": username,
|
||||||
|
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||||
|
})
|
||||||
|
return t.SignedString([]byte("secret"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyToken(jwtString string) (*jwt.Token, error) {
|
||||||
|
parse, err := jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte("secret"), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parse.Valid {
|
||||||
|
return nil, errors.New("invalid token")
|
||||||
|
}
|
||||||
|
if time.Unix(int64(parse.Claims.(jwt.MapClaims)["exp"].(float64)), 0).Before(time.Now()) {
|
||||||
|
return nil, errors.New("token is expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse, nil
|
||||||
|
}
|
38
store/db.go
Normal file
38
store/db.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"sonarqube-badge/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SQProject struct {
|
||||||
|
gorm.Model
|
||||||
|
ProjectName string `gorm:"unique"`
|
||||||
|
Token string `gorm:"unique"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Email string
|
||||||
|
Salt string
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDatabase(ctx context.Context) context.Context {
|
||||||
|
cfg := ctx.Value("config").(config.Config)
|
||||||
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s/sqbadge.db", cfg.DataPath)), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to open database")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.AutoMigrate(&SQProject{})
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to migrate database")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.WithValue(ctx, "db", db)
|
||||||
|
}
|
5
templates/index.templ
Normal file
5
templates/index.templ
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
templ Index() {
|
||||||
|
<form class></form>
|
||||||
|
}
|
29
templates/layout.templ
Normal file
29
templates/layout.templ
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
templ head(title string) {
|
||||||
|
<head>
|
||||||
|
<title>{ title }</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||||
|
</head>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ header() {
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark p-2">
|
||||||
|
<a class="navbar-brand">Sonarqube Badges</a>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Layout(component templ.Component, title string) {
|
||||||
|
<html lang="en">
|
||||||
|
@head(title)
|
||||||
|
<body hx-ext="response-targets">
|
||||||
|
@header()
|
||||||
|
<div class="container-fluid m-4">
|
||||||
|
@component
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||||
|
<script src="https://unpkg.com/htmx-ext-response-targets@2.0.3" integrity="sha384-T41oglUPvXLGBVyRdZsVRxNWnOOqCynaPubjUVjxhsjFTKrFJGEMm3/0KGmNQ+Pg" crossorigin="anonymous"></script>
|
||||||
|
</html>
|
||||||
|
}
|
26
templates/login.templ
Normal file
26
templates/login.templ
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
templ Login() {
|
||||||
|
<div class="container">
|
||||||
|
<form hx-post="/api/login" hx-target-error="#loginError" id="loginForm">
|
||||||
|
<div hx-trigger="changed" id="loginError"></div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email Address</label>
|
||||||
|
<input name="email" class="form-control" type="email" id="email">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input name="password" class="form-control" type="password" id="password">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Connect</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="application/javascript">
|
||||||
|
document.getElementById('loginForm').addEventListener('htmx:afterRequest', function (ev) {
|
||||||
|
if (ev.detail.successful) {
|
||||||
|
window.location.href = '/'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
}
|
Loading…
Reference in a new issue