package gitea import ( "code.gitea.io/sdk/gitea" "code.mrmelon54.com/melon/tools/utils" "context" _ "embed" "encoding/gob" "fmt" "github.com/google/uuid" "github.com/gorilla/mux" "golang.org/x/oauth2" "html/template" "net/http" "os" "path/filepath" "strings" ) //go:embed pages/index.go.html var indexTemplate string type Module struct { sessionWrapper func(cb func(http.ResponseWriter, *http.Request, *utils.State)) func(rw http.ResponseWriter, req *http.Request) oauthClient *oauth2.Config } type giteaKeyType int const ( KeyOauthClient = giteaKeyType(iota) KeyUser KeyState KeyAccessToken KeyRefreshToken ) func New() *Module { gob.Register(new(giteaKeyType)) return &Module{} } func (m *Module) GetName() string { return "Gitea" } func (m *Module) GetEndpoint() string { return "/gitea" } func (m *Module) SetupModule(router *mux.Router, f func(cb func(http.ResponseWriter, *http.Request, *utils.State)) func(rw http.ResponseWriter, req *http.Request)) { m.sessionWrapper = f m.oauthClient = &oauth2.Config{ ClientID: os.Getenv("GITEA_CLIENT_ID"), ClientSecret: os.Getenv("GITEA_CLIENT_SECRET"), Scopes: []string{"openid"}, Endpoint: oauth2.Endpoint{ AuthURL: os.Getenv("GITEA_AUTHORIZE_URL"), TokenURL: os.Getenv("GITEA_TOKEN_URL"), }, RedirectURL: os.Getenv("GITEA_REDIRECT_URL"), } router.HandleFunc("/", m.getClient(m.homepage)) router.HandleFunc("/login", m.sessionWrapper(m.loginPage)) } func (m *Module) getClient(cb func(http.ResponseWriter, *http.Request, *utils.State, *gitea.Client)) func(rw http.ResponseWriter, req *http.Request) { return m.sessionWrapper(func(rw http.ResponseWriter, req *http.Request, state *utils.State) { if v, ok := utils.GetStateValue[*gitea.Client](state, KeyOauthClient); ok { cb(rw, req, state, v) return } http.Redirect(rw, req, "/gitea/login", http.StatusTemporaryRedirect) }) } func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *utils.State, giteaClient *gitea.Client) { myUser, _, err := giteaClient.GetMyUserInfo() if err != nil { state.Del(KeyOauthClient) http.Error(rw, err.Error(), http.StatusInternalServerError) return } orgs, _, err := giteaClient.ListMyOrgs(gitea.ListOrgsOptions{}) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return } orgSimple := make([]struct{ Name string }, len(orgs)) for i, j := range orgs { orgSimple[i] = struct{ Name string }{j.UserName} } selOrg := "" myOrg := "" repoSimple := make([]struct { Name string Private bool }, 0) selRepo := "" selModule := "" selCommitTime := "" selCommitHash := "" mySpecs := make([]string, 0) q := req.URL.Query() if q.Has("org") { selOrg = q.Get("org") var repos []*gitea.Repository if selOrg == "!me" { myOrg = myUser.UserName repos, _, err = giteaClient.ListMyRepos(gitea.ListReposOptions{ListOptions: gitea.ListOptions{Page: 0, PageSize: 50}}) } else { myOrg = selOrg repos, _, err = giteaClient.ListOrgRepos(myOrg, gitea.ListOrgReposOptions{ListOptions: gitea.ListOptions{Page: 0, PageSize: 50}}) } if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return } repoSimple = make([]struct { Name string Private bool }, len(repos)) for i, j := range repos { repoSimple[i] = struct { Name string Private bool }{Name: j.Name, Private: j.Private} } if q.Has("repo") { selRepo = q.Get("repo") repo, resp, err := giteaClient.GetRepo(myOrg, selRepo) if err != nil { if resp.StatusCode != http.StatusNotFound { http.Error(rw, "GetRepo: "+err.Error(), http.StatusInternalServerError) return } } refs, resp, err := giteaClient.GetRepoRefs(myOrg, selRepo, "heads/"+repo.DefaultBranch) if err != nil { if resp.StatusCode != http.StatusNotFound { http.Error(rw, "GetRepoRefs: "+err.Error(), http.StatusInternalServerError) return } refs = make([]*gitea.Reference, 0) } if len(refs) == 1 { ref := refs[0] commit, _, err := giteaClient.GetSingleCommit(myOrg, selRepo, ref.Object.SHA) if err != nil { http.Error(rw, "GetSingleCommit: "+err.Error(), http.StatusInternalServerError) return } if q.Has("spec") { spec := q.Get("spec") specFile, _, err := giteaClient.GetFile(myOrg, selRepo, ref.Object.SHA, spec) if err != nil { http.Error(rw, "OpenAPI spec: "+err.Error(), http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "text/html") rw.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(rw, "Showing spec file: '%s'\n", spec) _, _ = fmt.Fprintf(rw, "\n
\n")
_, _ = rw.Write(specFile)
_, _ = fmt.Fprintf(rw, "\n
\n")
return
}
selCommitTime = commit.CommitMeta.Created.UTC().Format("20060102150405")
selCommitHash = commit.CommitMeta.SHA[:12]
goMod, resp, err := giteaClient.GetFile(myOrg, selRepo, ref.Object.SHA, "go.mod")
if err != nil {
if resp.StatusCode != http.StatusNotFound {
http.Error(rw, "go.mod: "+err.Error(), http.StatusInternalServerError)
return
}
}
goModStr := string(goMod)
goModIdx := strings.Index(goModStr, "\n")
goModLine := goModStr[:goModIdx]
goModSpace := strings.Index(goModLine, " ")
selModule = goModLine[goModSpace+1:]
trees, resp, err := giteaClient.GetTrees(myOrg, selRepo, ref.Object.SHA, true)
if err != nil {
if resp.StatusCode != http.StatusNotFound {
http.Error(rw, "%s: "+err.Error(), http.StatusInternalServerError)
return
}
}
for i := range trees.Entries {
switch filepath.Ext(trees.Entries[i].Path) {
case ".yml", ".yaml":
mySpecs = append(mySpecs, trees.Entries[i].Path)
}
}
}
}
}
tmp, err := template.New("homepage").Parse(indexTemplate)
if err != nil {
fmt.Println("Template parse error:", err)
return
}
err = tmp.Execute(rw, struct {
Username string
Orgs []struct{ Name string }
Repos []struct {
Name string
Private bool
}
MyOrg string
SelOrg string
SelRepo string
ShowOrg bool
SelModule string
ShowRepo bool
CommitTime string
CommitHash string
ShowSpec bool
Specs []string
}{
Username: myUser.UserName,
Orgs: orgSimple,
Repos: repoSimple,
MyOrg: myOrg,
SelOrg: selOrg,
SelRepo: selRepo,
SelModule: selModule,
ShowOrg: myOrg != "",
ShowRepo: selModule != "",
CommitTime: selCommitTime,
CommitHash: selCommitHash,
ShowSpec: len(mySpecs) > 0,
Specs: mySpecs,
})
if err != nil {
fmt.Println("Template execute error:", err)
return
}
}
func (m *Module) loginPage(rw http.ResponseWriter, req *http.Request, state *utils.State) {
if myUser, ok := utils.GetStateValue[*string](state, KeyUser); ok {
if myUser != nil {
http.Redirect(rw, req, "/gitea", http.StatusTemporaryRedirect)
return
}
}
if flowState, ok := utils.GetStateValue[uuid.UUID](state, KeyState); ok {
q := req.URL.Query()
if q.Has("code") && q.Has("state") {
if q.Get("state") == flowState.String() {
exchange, err := m.oauthClient.Exchange(context.Background(), q.Get("code"))
if err != nil {
fmt.Println("Exchange token error:", err)
return
}
c, err := gitea.NewClient(os.Getenv("GITEA_SERVER"), gitea.SetToken(exchange.AccessToken))
if err != nil {
fmt.Println("Create client error:", err)
return
}
state.Put(KeyOauthClient, c)
state.Put(KeyAccessToken, exchange.AccessToken)
state.Put(KeyRefreshToken, exchange.RefreshToken)
http.Redirect(rw, req, "/gitea", http.StatusTemporaryRedirect)
return
}
http.Error(rw, "OAuth flow state doesn't match\n", http.StatusBadRequest)
return
}
}
flowState := uuid.New()
state.Put(KeyState, flowState)
http.Redirect(rw, req, m.oauthClient.AuthCodeURL(flowState.String(), oauth2.AccessTypeOffline), http.StatusTemporaryRedirect)
}
func (m *Module) fetchRepos(giteaClient *gitea.Client) {
repos, _, err := giteaClient.ListOrgRepos("snow", gitea.ListOrgReposOptions{ListOptions: gitea.ListOptions{Page: 0, PageSize: 100}})
if err != nil {
fmt.Println(err)
return
}
for _, myRepo := range repos {
refs, _, err := giteaClient.GetRepoRefs("snow", myRepo.Name, "heads")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(refs))
for _, myRef := range refs {
if myRef.Ref == "refs/heads/"+myRepo.DefaultBranch {
fmt.Println(myRef.Ref)
return
}
}
fmt.Println("Can't find default branch")
}
}