Jonas Franz
5 years ago
16 changed files with 274 additions and 312 deletions
@ -1,42 +0,0 @@ |
|||||||
// staletea |
|
||||||
// Copyright (C) 2019 Jonas Franz |
|
||||||
// |
|
||||||
// This program is free software: you can redistribute it and/or modify |
|
||||||
// it under the terms of the GNU General Public License as published by |
|
||||||
// the Free Software Foundation, either version 3 of the License, or |
|
||||||
// (at your option) any later version. |
|
||||||
// |
|
||||||
// This program is distributed in the hope that it will be useful, |
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
// GNU General Public License for more details. |
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License |
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
|
|
||||||
package auth |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"gitea.com/jonasfranz/staletea/config" |
|
||||||
"golang.org/x/oauth2" |
|
||||||
) |
|
||||||
|
|
||||||
func endpoint() oauth2.Endpoint { |
|
||||||
// Endpoint is Google's OAuth 2.0 endpoint. |
|
||||||
return oauth2.Endpoint{ |
|
||||||
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", config.GiteaURL.Get().(string)), |
|
||||||
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", config.GiteaURL.Get().(string)), |
|
||||||
AuthStyle: oauth2.AuthStyleInParams, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Config returns an oauth2 config |
|
||||||
func Config() *oauth2.Config { |
|
||||||
return &oauth2.Config{ |
|
||||||
ClientID: config.GiteaClientID.Get().(string), |
|
||||||
ClientSecret: config.GiteaClientSecret.Get().(string), |
|
||||||
Endpoint: endpoint(), |
|
||||||
RedirectURL: fmt.Sprintf("%s/callback", config.BaseURL.Get().(string)), |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,21 @@ |
|||||||
|
// staletea |
||||||
|
// Copyright (C) 2020 Jonas Franz |
||||||
|
// |
||||||
|
// This program is free software: you can redistribute it and/or modify |
||||||
|
// it under the terms of the GNU General Public License as published by |
||||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||||
|
// (at your option) any later version. |
||||||
|
// |
||||||
|
// This program is distributed in the hope that it will be useful, |
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
// GNU General Public License for more details. |
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License |
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
package jobs |
||||||
|
|
||||||
|
func syncIssues() error { |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
// staletea |
||||||
|
// Copyright (C) 2020 Jonas Franz |
||||||
|
// |
||||||
|
// This program is free software: you can redistribute it and/or modify |
||||||
|
// it under the terms of the GNU General Public License as published by |
||||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||||
|
// (at your option) any later version. |
||||||
|
// |
||||||
|
// This program is distributed in the hope that it will be useful, |
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
// GNU General Public License for more details. |
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License |
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
package jobs |
||||||
|
|
||||||
|
import "github.com/robfig/cron" |
||||||
|
|
||||||
|
import "gitea.com/jonasfranz/staletea/config" |
||||||
|
|
||||||
|
type Job struct { |
||||||
|
Interval config.Key |
||||||
|
Action func() error |
||||||
|
} |
||||||
|
|
||||||
|
var registeredCronJobs []Job = []Job{ |
||||||
|
{Interval: config.RepositoryScanInterval, Action: syncRepositories}, |
||||||
|
{Interval: config.IssueScanInterval, Action: syncIssues}, |
||||||
|
} |
||||||
|
|
||||||
|
func handleError(unsafeFunc func() error) func() { |
||||||
|
return func() { |
||||||
|
err := unsafeFunc() |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func StartCronJobs() error { |
||||||
|
c := cron.New() |
||||||
|
for _, job := range registeredCronJobs { |
||||||
|
if err := c.AddFunc(job.Interval.Get().(string), handleError(job.Action)); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
c.Run() |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
// staletea |
||||||
|
// Copyright (C) 2020 Jonas Franz |
||||||
|
// |
||||||
|
// This program is free software: you can redistribute it and/or modify |
||||||
|
// it under the terms of the GNU General Public License as published by |
||||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||||
|
// (at your option) any later version. |
||||||
|
// |
||||||
|
// This program is distributed in the hope that it will be useful, |
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
// GNU General Public License for more details. |
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License |
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
package jobs |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"gitea.com/jonasfranz/staletea/config" |
||||||
|
"gitea.com/jonasfranz/staletea/loader" |
||||||
|
"gitea.com/jonasfranz/staletea/models" |
||||||
|
) |
||||||
|
|
||||||
|
func syncRepositories() error { |
||||||
|
savedRepos, err := models.FindAllRepositories() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
savedRepoIDs := make(map[int64]*models.Repository) |
||||||
|
for _, savedRepo := range savedRepos { |
||||||
|
savedRepoIDs[savedRepo.ID] = savedRepo |
||||||
|
} |
||||||
|
l := loader.NewGiteaLoader(config.GiteaURL.Get().(string), config.BotAccessToken.Get().(string)) |
||||||
|
loadedRepos, err := l.LoadRepositories() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
newRepos := make([]*models.Repository, 0) |
||||||
|
for _, loadedRepo := range loadedRepos { |
||||||
|
if _, ok := savedRepoIDs[loadedRepo.ID]; !ok { |
||||||
|
repoConfig, err := l.DownloadConfig(loadedRepo) |
||||||
|
if err != nil { |
||||||
|
fmt.Printf("cannot download config for repo %s/%s: %v\n", loadedRepo.Owner, loadedRepo.Name, err) |
||||||
|
continue |
||||||
|
} |
||||||
|
newRepos = append(newRepos, &models.Repository{ |
||||||
|
ID: loadedRepo.ID, |
||||||
|
Config: repoConfig, |
||||||
|
Activated: true, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
return models.CreateRepositories(newRepos) |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
// staletea |
||||||
|
// Copyright (C) 2020 Jonas Franz |
||||||
|
// |
||||||
|
// This program is free software: you can redistribute it and/or modify |
||||||
|
// it under the terms of the GNU General Public License as published by |
||||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||||
|
// (at your option) any later version. |
||||||
|
// |
||||||
|
// This program is distributed in the hope that it will be useful, |
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
// GNU General Public License for more details. |
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License |
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
package loader |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"code.gitea.io/sdk/gitea" |
||||||
|
"gitea.com/jonasfranz/staletea/config" |
||||||
|
"gopkg.in/yaml.v2" |
||||||
|
) |
||||||
|
|
||||||
|
func NewGiteaLoader(url, token string) Loader { |
||||||
|
return &GiteaLoader{ |
||||||
|
client: gitea.NewClient(url, token), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type GiteaLoader struct { |
||||||
|
client *gitea.Client |
||||||
|
} |
||||||
|
|
||||||
|
func (l *GiteaLoader) LoadRepositories() ([]*Repository, error) { |
||||||
|
repos, err := l.client.ListMyRepos() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
convertedRepos := make([]*Repository, len(repos)) |
||||||
|
for index, repo := range repos { |
||||||
|
convertedRepos[index] = &Repository{ |
||||||
|
ID: repo.ID, |
||||||
|
Owner: repo.Owner.UserName, |
||||||
|
Name: repo.Name, |
||||||
|
ConfigBranch: repo.DefaultBranch, |
||||||
|
} |
||||||
|
} |
||||||
|
return convertedRepos, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *GiteaLoader) DownloadConfig(repo *Repository) (*RepositoryConfig, error) { |
||||||
|
file, err := l.client.GetFile(repo.Owner, repo.Name, repo.ConfigBranch, config.RepositoryConfigFileName.Get().(string)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
repoConfig := new(RepositoryConfig) |
||||||
|
if err := yaml.NewDecoder(bytes.NewReader(file)).Decode(repoConfig); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return repoConfig, nil |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
// staletea |
||||||
|
// Copyright (C) 2020 Jonas Franz |
||||||
|
// |
||||||
|
// This program is free software: you can redistribute it and/or modify |
||||||
|
// it under the terms of the GNU General Public License as published by |
||||||
|
// the Free Software Foundation, either version 3 of the License, or |
||||||
|
// (at your option) any later version. |
||||||
|
// |
||||||
|
// This program is distributed in the hope that it will be useful, |
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
// GNU General Public License for more details. |
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License |
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
package loader |
||||||
|
|
||||||
|
type Loader interface { |
||||||
|
LoadRepositories() ([]*Repository, error) |
||||||
|
DownloadConfig(repo *Repository) (*RepositoryConfig, error) |
||||||
|
} |
@ -1,83 +0,0 @@ |
|||||||
// staletea |
|
||||||
// Copyright (C) 2019 Jonas Franz |
|
||||||
// |
|
||||||
// This program is free software: you can redistribute it and/or modify |
|
||||||
// it under the terms of the GNU General Public License as published by |
|
||||||
// the Free Software Foundation, either version 3 of the License, or |
|
||||||
// (at your option) any later version. |
|
||||||
// |
|
||||||
// This program is distributed in the hope that it will be useful, |
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
// GNU General Public License for more details. |
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License |
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
|
|
||||||
package web |
|
||||||
|
|
||||||
import ( |
|
||||||
"code.gitea.io/sdk/gitea" |
|
||||||
"context" |
|
||||||
"gitea.com/jonasfranz/staletea/auth" |
|
||||||
"gitea.com/jonasfranz/staletea/config" |
|
||||||
"gitea.com/jonasfranz/staletea/models" |
|
||||||
"gitea.com/jonasfranz/staletea/utils" |
|
||||||
"github.com/gin-contrib/sessions" |
|
||||||
"github.com/gin-gonic/gin" |
|
||||||
"net/http" |
|
||||||
) |
|
||||||
|
|
||||||
func handleLogin(ctx *gin.Context) { |
|
||||||
session := sessions.Default(ctx) |
|
||||||
state, err := utils.NewSecret(32) |
|
||||||
if err != nil { |
|
||||||
_ = ctx.AbortWithError(500, err) |
|
||||||
return |
|
||||||
} |
|
||||||
session.Set("state", state) |
|
||||||
if err := session.Save(); err != nil { |
|
||||||
_ = ctx.AbortWithError(http.StatusInternalServerError, err) |
|
||||||
return |
|
||||||
} |
|
||||||
redirectURL := auth.Config().AuthCodeURL(state) |
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, redirectURL) |
|
||||||
} |
|
||||||
|
|
||||||
func handleCallback(ctx *gin.Context) { |
|
||||||
session := sessions.Default(ctx) |
|
||||||
state := ctx.Query("state") |
|
||||||
savedState := session.Get("state") |
|
||||||
if savedState == nil { |
|
||||||
ctx.String(http.StatusUnauthorized, "Invalid state") |
|
||||||
return |
|
||||||
} |
|
||||||
session.Delete("state") |
|
||||||
if parsedState, ok := savedState.(string); !ok || parsedState != state { |
|
||||||
ctx.String(http.StatusUnauthorized, "Invalid state") |
|
||||||
return |
|
||||||
} |
|
||||||
token, err := auth.Config().Exchange(ctx, ctx.Query("code")) |
|
||||||
if err != nil { |
|
||||||
_ = ctx.AbortWithError(http.StatusUnauthorized, err) |
|
||||||
return |
|
||||||
} |
|
||||||
client := gitea.NewClient(config.GiteaURL.Get().(string), "") |
|
||||||
client.SetHTTPClient(auth.Config().Client(context.Background(), token)) |
|
||||||
user, err := client.GetMyUserInfo() |
|
||||||
if err != nil { |
|
||||||
_ = ctx.AbortWithError(http.StatusUnauthorized, err) |
|
||||||
return |
|
||||||
} |
|
||||||
storedUser := &models.User{ |
|
||||||
ID: user.ID, |
|
||||||
Username: user.UserName, |
|
||||||
Token: token, |
|
||||||
} |
|
||||||
session.Set("user", storedUser) |
|
||||||
if err := session.Save(); err != nil { |
|
||||||
_ = ctx.AbortWithError(http.StatusInternalServerError, err) |
|
||||||
return |
|
||||||
} |
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, config.BaseURL.Get().(string)) |
|
||||||
} |
|
@ -1,71 +0,0 @@ |
|||||||
// staletea |
|
||||||
// Copyright (C) 2019 Jonas Franz |
|
||||||
// |
|
||||||
// This program is free software: you can redistribute it and/or modify |
|
||||||
// it under the terms of the GNU General Public License as published by |
|
||||||
// the Free Software Foundation, either version 3 of the License, or |
|
||||||
// (at your option) any later version. |
|
||||||
// |
|
||||||
// This program is distributed in the hope that it will be useful, |
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
// GNU General Public License for more details. |
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License |
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
|
|
||||||
package web |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"gitea.com/jonasfranz/staletea/config" |
|
||||||
"gitea.com/jonasfranz/staletea/models" |
|
||||||
"github.com/gin-contrib/sessions" |
|
||||||
"github.com/gin-gonic/gin" |
|
||||||
"net/http" |
|
||||||
) |
|
||||||
|
|
||||||
func showDashboard(ctx *gin.Context) { |
|
||||||
session := sessions.Default(ctx) |
|
||||||
user, ok := session.Get("user").(*models.User) |
|
||||||
if !ok || user == nil { |
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/login", config.BaseURL.Get().(string))) |
|
||||||
return |
|
||||||
} |
|
||||||
activatedRepos, err := models.FindRepositoriesByUserID(user.ID) |
|
||||||
if err != nil { |
|
||||||
_ = ctx.AbortWithError(500, err) |
|
||||||
return |
|
||||||
} |
|
||||||
remoteRepos, err := user.GiteaClient().ListMyRepos() |
|
||||||
if err != nil { |
|
||||||
_ = ctx.AbortWithError(500, err) |
|
||||||
return |
|
||||||
} |
|
||||||
combinedRepos := make(map[int64]*models.Repository, len(activatedRepos)) |
|
||||||
|
|
||||||
for _, repo := range activatedRepos { |
|
||||||
combinedRepos[repo.ID] = repo |
|
||||||
} |
|
||||||
|
|
||||||
for _, repo := range remoteRepos { |
|
||||||
combinedRepos[repo.ID] = &models.Repository{ |
|
||||||
ID: repo.ID, |
|
||||||
UserID: user.ID, |
|
||||||
Owner: repo.Owner.UserName, |
|
||||||
Name: repo.Name, |
|
||||||
Activated: false, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
data := map[string]interface{}{ |
|
||||||
"repos": combinedRepos, |
|
||||||
"user": user, |
|
||||||
} |
|
||||||
|
|
||||||
ctx.HTML(200, "dashboard.tmpl", data) |
|
||||||
} |
|
||||||
|
|
||||||
func handleActivate(ctx *gin.Context) { |
|
||||||
|
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
// staletea |
|
||||||
// Copyright (C) 2019 Jonas Franz |
|
||||||
// |
|
||||||
// This program is free software: you can redistribute it and/or modify |
|
||||||
// it under the terms of the GNU General Public License as published by |
|
||||||
// the Free Software Foundation, either version 3 of the License, or |
|
||||||
// (at your option) any later version. |
|
||||||
// |
|
||||||
// This program is distributed in the hope that it will be useful, |
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
// GNU General Public License for more details. |
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License |
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
||||||
|
|
||||||
package web |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/gob" |
|
||||||
"gitea.com/jonasfranz/staletea/config" |
|
||||||
"gitea.com/jonasfranz/staletea/models" |
|
||||||
"github.com/gin-contrib/sessions" |
|
||||||
"github.com/gin-contrib/sessions/cookie" |
|
||||||
"github.com/gin-gonic/gin" |
|
||||||
) |
|
||||||
|
|
||||||
// StartServer will start the webserver at the given addresses |
|
||||||
func StartServer(addr ...string) error { |
|
||||||
r := gin.Default() |
|
||||||
gob.Register(new(models.User)) |
|
||||||
store := cookie.NewStore([]byte(config.SessionSecret.Get().(string))) |
|
||||||
r.Use(sessions.Sessions("sessions", store)) |
|
||||||
r.LoadHTMLFiles("templates/dashboard.tmpl") |
|
||||||
setupRoutes(r) |
|
||||||
return r.Run(addr...) |
|
||||||
} |
|
||||||
|
|
||||||
func setupRoutes(r *gin.Engine) { |
|
||||||
r.GET("/", showDashboard) |
|
||||||
r.POST("/", handleActivate) |
|
||||||
r.GET("/login", handleLogin) |
|
||||||
r.GET("/callback", handleCallback) |
|
||||||
} |
|
Loading…
Reference in new issue