Amorçage du projet

This commit is contained in:
2019-11-28 11:50:51 +01:00
commit 18f2dbf592
33 changed files with 8915 additions and 0 deletions

34
cmd/server/container.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"forge.cadoles.com/wpetit/gitea-kan/cmd/server/config"
"github.com/gorilla/sessions"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/service"
"gitlab.com/wpetit/goweb/service/session"
"gitlab.com/wpetit/goweb/session/gorilla"
)
func getServiceContainer(conf *config.Config) (*service.Container, error) {
// Initialize and configure service container
ctn := service.NewContainer()
// Create and initialize HTTP session service provider
cookieStore, err := gorilla.CreateCookieSessionStore(64, 32, &sessions.Options{
Path: "/",
HttpOnly: true,
})
if err != nil {
return nil, errors.Wrap(err, "could not create cookie store")
}
ctn.Provide(
session.ServiceName,
gorilla.ServiceProvider("gitea-kan", cookieStore),
)
// Create and expose config service provider
ctn.Provide(config.ServiceName, config.ServiceProvider(conf))
return ctn, nil
}

87
cmd/server/main.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"flag"
"log"
"net/http"
"os"
"forge.cadoles.com/wpetit/gitea-kan/cmd/server/route"
"forge.cadoles.com/wpetit/gitea-kan/config"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
)
//nolint: gochecknoglobals
var (
configFile = ""
workdir = ""
dumpConfig = false
)
//nolint: gochecknoinits
func init() {
flag.StringVar(&configFile, "config", configFile, "configuration file")
flag.StringVar(&workdir, "workdir", workdir, "working directory")
flag.BoolVar(&dumpConfig, "dump-config", dumpConfig, "dump configuration and exit")
}
func main() {
flag.Parse()
// Switch to new working directory if defined
if workdir != "" {
if err := os.Chdir(workdir); err != nil {
panic(errors.Wrapf(err, "change working directory to '%s'", workdir))
}
}
// Load configuration file if defined, use default configuration otherwise
var conf *config.Config
var err error
if configFile != "" {
conf, err = config.NewFromFile(configFile)
if err != nil {
panic(errors.Wrapf(err, "load config file '%s'", configFile))
}
} else {
conf = config.NewDefault()
}
// Dump configuration if asked
if dumpConfig {
if err := config.Dump(conf, os.Stdout); err != nil {
log.Fatalf("%+v", errors.Wrapf(err, "dump config"))
}
os.Exit(0)
}
r := chi.NewRouter()
// Define base middlewares
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Create service container
ctn, err := getServiceContainer(conf)
if err != nil {
log.Fatalf("%+v", errors.Wrap(err, "create service container"))
}
// Expose service container on router
r.Use(container.ServiceContainer(ctn))
// Define routes
route.Mount(r, conf)
log.Printf("listening on '%s'", conf.HTTP.Address)
if err := http.ListenAndServe(conf.HTTP.Address, r); err != nil {
log.Fatalf("%+v", errors.Wrapf(err, "listen '%s'", conf.HTTP.Address))
}
}

View File

@ -0,0 +1,31 @@
package route
import (
"crypto/rand"
"net/http"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/template"
"gitlab.com/wpetit/goweb/template/html"
)
func extendTemplateData(w http.ResponseWriter, r *http.Request, data template.Data) template.Data {
ctn := container.Must(r.Context())
data, err := template.Extend(data,
html.WithFlashes(w, r, ctn),
)
if err != nil {
panic(errors.Wrap(err, "error while extending template data"))
}
return data
}
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}

View File

@ -0,0 +1,23 @@
package route
import (
"net/http"
"forge.cadoles.com/wpetit/gitea-kan/config"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"
)
func handleLogout(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
conf := config.Must(ctn)
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
if err := sess.Delete(w, r); err != nil {
panic(errors.Wrap(err, "could not delete session"))
}
http.Redirect(w, r, conf.Gitea.LogoutURL, http.StatusSeeOther)
}

View File

@ -0,0 +1,46 @@
package route
import (
"net/http"
"forge.cadoles.com/wpetit/gitea-kan/middleware"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"
"golang.org/x/oauth2"
)
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
expectedState := sess.Get(middleware.SessionOAuth2State)
state := r.FormValue("state")
if state != expectedState {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
giteaOAuth2Config := middleware.GiteaOAuth2Config(ctn)
code := r.FormValue("code")
token, err := giteaOAuth2Config.Exchange(oauth2.NoContext, code)
if err != nil {
panic(errors.Wrap(err, "could not exchange oauth2 token"))
}
sess.Set(middleware.SessionOAuth2AccessToken, token.AccessToken)
sess.Set(middleware.SessionOAuth2State, "")
if err := sess.Save(w, r); err != nil {
panic(errors.Wrap(err, "could not save session"))
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}

41
cmd/server/route/proxy.go Normal file
View File

@ -0,0 +1,41 @@
package route
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"forge.cadoles.com/wpetit/gitea-kan/config"
"forge.cadoles.com/wpetit/gitea-kan/middleware"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"
)
func proxyAPIRequest(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
conf := config.Must(ctn)
apiBaseURL, err := url.Parse(conf.Gitea.APIBaseURL)
if err != nil {
panic(errors.Wrap(err, "could not parse api base url"))
}
sess, err := session.Must(ctn).Get(w, r)
if err != nil {
panic(errors.Wrap(err, "could not retrieve session"))
}
accessToken := sess.Get(middleware.SessionOAuth2AccessToken)
proxy := httputil.NewSingleHostReverseProxy(apiBaseURL)
proxy.Director = func(r *http.Request) {
r.Host = apiBaseURL.Host
r.URL.Scheme = apiBaseURL.Scheme
r.URL.Host = apiBaseURL.Host
r.Header.Add("Authorization", fmt.Sprintf("token %s", accessToken))
}
proxy.ServeHTTP(w, r)
}

33
cmd/server/route/route.go Normal file
View File

@ -0,0 +1,33 @@
package route
import (
"net/http"
"path"
"forge.cadoles.com/wpetit/gitea-kan/config"
"forge.cadoles.com/wpetit/gitea-kan/middleware"
"github.com/go-chi/chi"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/static"
)
func Mount(r *chi.Mux, config *config.Config) {
r.Group(func(r chi.Router) {
r.Get("/callback", handleOAuth2Callback)
// Authenticated routes
r.Group(func(r chi.Router) {
r.Use(middleware.Authenticate)
r.Get("/logout", handleLogout)
r.Get("/gitea/api/*", http.StripPrefix("/gitea", http.HandlerFunc(proxyAPIRequest)).ServeHTTP)
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", html5PushStateHandler))
})
})
}
func html5PushStateHandler(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
conf := config.Must(ctn)
indexFile := path.Join(conf.HTTP.PublicDir, "index.html")
http.ServeFile(w, r, indexFile)
}

View File

@ -0,0 +1,17 @@
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{block "title" . -}}{{- end}}</title>
<link rel="stylesheet" href="css/main.css">
{{- block "head_style" . -}}{{end}}
{{- block "head_script" . -}}{{end}}
</head>
<body>
{{- block "body" . -}}{{- end -}}
{{- block "body_script" . -}}{{end}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,8 @@
{{define "title"}}Gitea Apps{{end}}
{{define "body"}}
<div id="gitea-apps" class="is-fullheight"></div>
{{end}}
{{define "body_scripts"}}
<script type="text/javascript" src="main.js"></script>
{{end}}
{{template "base" .}}