diff --git a/main.go b/main.go index 8f45636..a58d99a 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func main() { log.Fatal().Err(err).Msg("") } - r := router.New(router.Config{ + r := router.New(router.App{ Domain: *domainFlag, SessionSecret: *sessionSecretFlag, GiteaURL: *giteaURLFlag, diff --git a/router/app.go b/router/app.go new file mode 100644 index 0000000..bb3a40a --- /dev/null +++ b/router/app.go @@ -0,0 +1,32 @@ +package router + +import ( + "io" + "net/http" + + "go.jolheiser.com/gistea/router/session" + "go.jolheiser.com/gistea/static" +) + +type App struct { + Domain string + SessionSecret string + + GiteaURL string + GiteaClientKey string + GiteaClientSecret string + + store *session.Store +} + +func (a *App) Tmpl(w io.Writer, r *http.Request, page static.Page, data map[string]any) error { + info, _ := a.store.Info(r) + ctx := static.Context{ + "Domain": a.Domain, + "GiteaURL": a.GiteaURL, + "Page": page, + "Session": info, + "Data": data, + } + return static.Tmpl(w, static.Base, ctx) +} diff --git a/router/gist.go b/router/gist.go new file mode 100644 index 0000000..5028239 --- /dev/null +++ b/router/gist.go @@ -0,0 +1,22 @@ +package router + +import ( + "net/http" + + "go.jolheiser.com/gistea/static" +) + +func (a *App) newGist(w http.ResponseWriter, r *http.Request) { + page := static.New + + repos, err := a.store.Repos(r) + if err != nil { + page = static.Landing + } + + if err := a.Tmpl(w, r, page, map[string]any{ + "Repos": repos, + }); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} diff --git a/router/oauth.go b/router/oauth.go new file mode 100644 index 0000000..4940a49 --- /dev/null +++ b/router/oauth.go @@ -0,0 +1,34 @@ +package router + +import ( + "net/http" + + "github.com/markbates/goth/gothic" +) + +func (a *App) login(w http.ResponseWriter, r *http.Request) { + user, err := gothic.CompleteUserAuth(w, r) + if err != nil { + gothic.BeginAuthHandler(w, r) + return + } + + if err := a.store.CompleteAuth(w, r, user.AccessToken); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + http.Redirect(w, r, a.Domain, http.StatusFound) +} + +func (a *App) loginCallback(w http.ResponseWriter, r *http.Request) { + user, err := gothic.CompleteUserAuth(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + if err := a.store.CompleteAuth(w, r, user.AccessToken); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + http.Redirect(w, r, a.Domain, http.StatusFound) +} diff --git a/router/router.go b/router/router.go index ba740b4..03b37a8 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,7 @@ import ( "net/http" "strings" + "go.jolheiser.com/gistea/router/session" "go.jolheiser.com/gistea/static" "github.com/go-chi/chi/v5" @@ -16,35 +17,27 @@ import ( "github.com/rs/zerolog/log" ) -type Config struct { - Domain string - SessionSecret string - - GiteaURL string - GiteaClientKey string - GiteaClientSecret string -} - -func New(cfg Config) *chi.Mux { - cfg.GiteaURL = strings.TrimRight(cfg.GiteaURL, "/") - cfg.Domain = strings.TrimRight(cfg.Domain, "/") - callbackURL := fmt.Sprintf("%s/auth/callback", cfg.Domain) +func New(app App) *chi.Mux { + app.GiteaURL = strings.TrimRight(app.GiteaURL, "/") + app.Domain = strings.TrimRight(app.Domain, "/") + callbackURL := fmt.Sprintf("%s/_/auth/callback", app.Domain) goth.UseProviders( - gitea.NewCustomisedURL(cfg.GiteaClientKey, cfg.GiteaClientSecret, callbackURL, - fmt.Sprintf("%s/login/oauth/authorize", cfg.GiteaURL), - fmt.Sprintf("%s/login/oauth/access_token", cfg.GiteaURL), - fmt.Sprintf("%s/api/v1/user", cfg.GiteaURL), + gitea.NewCustomisedURL(app.GiteaClientKey, app.GiteaClientSecret, callbackURL, + fmt.Sprintf("%s/login/oauth/authorize", app.GiteaURL), + fmt.Sprintf("%s/login/oauth/access_token", app.GiteaURL), + fmt.Sprintf("%s/api/v1/user", app.GiteaURL), ), ) - gothStore := sessions.NewCookieStore([]byte(cfg.GiteaClientSecret)) + gothStore := sessions.NewCookieStore([]byte(app.GiteaClientSecret)) gothStore.Options.HttpOnly = true gothic.Store = gothStore gothic.GetProviderName = func(_ *http.Request) (string, error) { return "gitea", nil } - store := NewSessionStore(cfg.SessionSecret, cfg.GiteaURL) + store := session.NewStore(app.SessionSecret, app.GiteaURL) + app.store = store r := chi.NewMux() r.Use(middleware.Logger) @@ -55,50 +48,18 @@ func New(cfg Config) *chi.Mux { NoColor: true, }) - r.Route("/", func(r chi.Router) { - r.Use(store.Middleware) - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - repos, err := store.Repos(r) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + r.Route("/_", func(r chi.Router) { + r.Mount("/css", static.CSS) + r.Mount("/js", static.JS) - if err := static.Tmpl(w, static.Base, static.Context{ - "repos": repos, - "page": static.New, - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } + r.Route("/auth", func(r chi.Router) { + r.Get("/login", app.login) + r.Get("/callback", app.loginCallback) }) }) - r.Route("/auth", func(r chi.Router) { - r.Get("/login", func(w http.ResponseWriter, r *http.Request) { - user, err := gothic.CompleteUserAuth(w, r) - if err != nil { - gothic.BeginAuthHandler(w, r) - return - } - - if err := store.Auth(w, r, user.AccessToken); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - http.Redirect(w, r, cfg.Domain, http.StatusFound) - }) - r.Get("/callback", func(w http.ResponseWriter, r *http.Request) { - user, err := gothic.CompleteUserAuth(w, r) - if err != nil { - http.Error(w, err.Error(), http.StatusUnauthorized) - return - } - if err := store.Auth(w, r, user.AccessToken); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - http.Redirect(w, r, cfg.Domain, http.StatusFound) - }) + r.Route("/", func(r chi.Router) { + r.Get("/", app.newGist) }) return r diff --git a/router/session.go b/router/session/session.go similarity index 55% rename from router/session.go rename to router/session/session.go index 5139f32..dbb9674 100644 --- a/router/session.go +++ b/router/session/session.go @@ -1,6 +1,8 @@ -package router +package session import ( + "encoding/gob" + "errors" "fmt" "net/http" @@ -9,14 +11,26 @@ import ( "github.com/markbates/goth/gothic" ) -const sessionCookie = "_gistea_session" +const ( + sessionCookie = "_gistea_session" + infoKey = "_gistea_info" +) -type SessionStore struct { +type Store struct { Store sessions.Store GiteaURL string } -func (s *SessionStore) Middleware(next http.Handler) http.Handler { +type Info struct { + Org string + Token string +} + +func init() { + gob.Register(&Info{}) +} + +func (s *Store) RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sess, err := s.Store.Get(r, sessionCookie) if err != nil { @@ -24,7 +38,7 @@ func (s *SessionStore) Middleware(next http.Handler) http.Handler { return } - if _, ok := sess.Values["authenticated"]; !ok { + if _, ok := sess.Values[infoKey]; !ok { gothic.BeginAuthHandler(w, r) return } @@ -33,7 +47,25 @@ func (s *SessionStore) Middleware(next http.Handler) http.Handler { }) } -func (s *SessionStore) Auth(w http.ResponseWriter, r *http.Request, token string) error { +func (s *Store) Info(r *http.Request) (*Info, error) { + sess, err := s.Store.Get(r, sessionCookie) + if err != nil { + return nil, err + } + if infoAny, ok := sess.Values[infoKey]; ok { + if info, ok := infoAny.(*Info); ok { + return info, nil + } + } + return nil, errors.New("could not get session info") +} + +func (s *Store) HasAuth(r *http.Request) bool { + i, _ := s.Info(r) + return i != nil +} + +func (s *Store) CompleteAuth(w http.ResponseWriter, r *http.Request, token string) error { client, err := gitea.NewClient(s.GiteaURL, gitea.SetToken(token)) if err != nil { return err @@ -63,39 +95,31 @@ func (s *SessionStore) Auth(w http.ResponseWriter, r *http.Request, token string return err } - sess.Values["authenticated"] = true - sess.Values["org"] = org - sess.Values["token"] = token + sess.Values[infoKey] = &Info{ + Org: org, + Token: token, + } return s.Store.Save(r, w, sess) } -func (s *SessionStore) client(r *http.Request) (*gitea.Client, error) { - sess, err := s.Store.Get(r, sessionCookie) +func (s *Store) Repos(r *http.Request) ([]*gitea.Repository, error) { + info, err := s.Info(r) if err != nil { return nil, err } - return gitea.NewClient(s.GiteaURL, gitea.SetToken(sess.Values["token"].(string))) -} - -func (s *SessionStore) Repos(r *http.Request) ([]*gitea.Repository, error) { - sess, err := s.Store.Get(r, sessionCookie) + client, err := gitea.NewClient(s.GiteaURL, gitea.SetToken(info.Token)) if err != nil { return nil, err } - client, err := s.client(r) - if err != nil { - return nil, err - } - - repos, _, err := client.ListOrgRepos(sess.Values["org"].(string), gitea.ListOrgReposOptions{}) + repos, _, err := client.ListOrgRepos(info.Org, gitea.ListOrgReposOptions{}) return repos, err } -func NewSessionStore(sessionSecret, giteURL string) *SessionStore { +func NewStore(sessionSecret, giteURL string) *Store { store := sessions.NewCookieStore([]byte(sessionSecret)) - return &SessionStore{ + return &Store{ Store: store, GiteaURL: giteURL, } diff --git a/static/css/gistea.css b/static/css/gistea.css new file mode 100644 index 0000000..245e0b9 --- /dev/null +++ b/static/css/gistea.css @@ -0,0 +1,11 @@ +.form-input { + border-radius: 10px; + margin: 1em 0; +} + +.form-head { + border: black solid thin; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + padding: 0 1em; +} \ No newline at end of file diff --git a/static/js/monaco.js b/static/js/monaco.js new file mode 100644 index 0000000..7dc8560 --- /dev/null +++ b/static/js/monaco.js @@ -0,0 +1,39 @@ +require.config({paths: {'vs': 'https://unpkg.com/monaco-editor@latest/min/vs'}}); + +// Before loading vs/editor/editor.main, define a global MonacoEnvironment that overwrites +// the default worker url location (used when creating WebWorkers). The problem here is that +// HTML5 does not allow cross-domain web workers, so we need to proxy the instantiation of +// a web worker through a same-domain script +window.MonacoEnvironment = { + getWorkerUrl: function (workerId, label) { + return `data:text/javascript;charset=utf-8,${encodeURIComponent(` + self.MonacoEnvironment = { + baseUrl: 'https://unpkg.com/monaco-editor@latest/min/' + }; + importScripts('https://unpkg.com/monaco-editor@latest/min/vs/base/worker/workerMain.js');` + )}`; + } +}; + +let editors = []; + +require(["vs/editor/editor.main"], function () { + let $filenames = document.querySelectorAll(".monaco-filename"); + document.querySelectorAll('.monaco-editor').forEach((elem, idx) => { + let $editor = monaco.editor.create(elem, { + language: 'text', + theme: 'vs-dark', + }); + elem.classList.remove("loading", "loading-lg"); + let $filename = $filenames[idx]; + editors.push({ + "filename": $filename, + "editor": $editor + }); + $filename.addEventListener('change', () => { + monaco.editor.setModelLanguage($editor.getModel(), $filename.value.split(".").pop()); + }); + }) + + //monaco.editor.setModelLanguage($editor.getModel(), "go"); +}); \ No newline at end of file diff --git a/static/static.go b/static/static.go index e81a42f..26fc7b5 100644 --- a/static/static.go +++ b/static/static.go @@ -1,49 +1,16 @@ package static import ( - "bytes" - _ "embed" - "fmt" - "html/template" - "io" - - "github.com/rs/zerolog/log" + "embed" + "net/http" ) -type Context = map[string]any - var ( - templates = make(map[string]*template.Template) + //go:embed css + css embed.FS + CSS = http.StripPrefix("/_", http.FileServer(http.FS(css))) - //go:embed templates/base.tmpl - baseTmpl string - Base = "base" - - //go:embed templates/new.tmpl - newTmpl string - New = "new" + //go:embed js + js embed.FS + JS = http.StripPrefix("/_", http.FileServer(http.FS(js))) ) - -func init() { - templates[Base] = template.Must(template.New("").Funcs(funcMap).Parse(baseTmpl)) - templates[New] = template.Must(template.New("").Funcs(funcMap).Parse(newTmpl)) -} - -func Tmpl(w io.Writer, name string, ctx any) error { - if tmpl, ok := templates[name]; ok { - return tmpl.Execute(w, ctx) - } - return fmt.Errorf("unknown template %q", name) -} - -var funcMap = template.FuncMap{ - "tmpl": func(name string, ctx any) template.HTML { - var buf bytes.Buffer - err := Tmpl(&buf, name, ctx) - if err != nil { - log.Err(err).Msg("") - return "" - } - return template.HTML(buf.String()) - }, -} diff --git a/static/templates.go b/static/templates.go new file mode 100644 index 0000000..1f52af4 --- /dev/null +++ b/static/templates.go @@ -0,0 +1,56 @@ +package static + +import ( + "bytes" + _ "embed" + "fmt" + "html/template" + "io" + + "github.com/rs/zerolog/log" +) + +type Context map[string]any + +type Page string + +var ( + templates = make(map[Page]*template.Template) + + //go:embed templates/base.tmpl + baseTmpl string + Base Page = "base" + + //go:embed templates/new.tmpl + newTmpl string + New Page = "new" + + //go:embed templates/landing.tmpl + landingTmpl string + Landing Page = "landing" +) + +func init() { + templates[Base] = template.Must(template.New("").Funcs(funcMap).Parse(baseTmpl)) + templates[New] = template.Must(template.New("").Funcs(funcMap).Parse(newTmpl)) + templates[Landing] = template.Must(template.New("").Funcs(funcMap).Parse(landingTmpl)) +} + +func Tmpl(w io.Writer, name Page, ctx Context) error { + if tmpl, ok := templates[name]; ok { + return tmpl.Execute(w, ctx) + } + return fmt.Errorf("unknown template %q", name) +} + +var funcMap = template.FuncMap{ + "tmpl": func(name Page, ctx Context) template.HTML { + var buf bytes.Buffer + err := Tmpl(&buf, name, ctx) + if err != nil { + log.Err(err).Msg("") + return "" + } + return template.HTML(buf.String()) + }, +} diff --git a/static/templates/base.tmpl b/static/templates/base.tmpl index 07e0079..db70389 100644 --- a/static/templates/base.tmpl +++ b/static/templates/base.tmpl @@ -6,19 +6,25 @@ + - +
- {{if .page}}{{tmpl .page .}}{{end}} + {{tmpl .Page .}}
\ No newline at end of file diff --git a/static/templates/landing.tmpl b/static/templates/landing.tmpl new file mode 100644 index 0000000..db57158 --- /dev/null +++ b/static/templates/landing.tmpl @@ -0,0 +1,7 @@ +
+
+
+ Please login +
+
+
diff --git a/static/templates/new.tmpl b/static/templates/new.tmpl index 1fdced5..3e6d8ab 100644 --- a/static/templates/new.tmpl +++ b/static/templates/new.tmpl @@ -1,43 +1,19 @@
- +
-
+
+ +
+
- +