From baf013f834d7a56f6e15bc977e9f53aed1c318fd Mon Sep 17 00:00:00 2001 From: Massaki Archambault Date: Sun, 4 Aug 2024 21:00:34 -0400 Subject: [PATCH] add support for github users --- platforms/github/client.go | 17 +++++ platforms/github/organization.go | 1 + platforms/github/user.go | 104 +++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/platforms/github/client.go b/platforms/github/client.go index 8381519..2cf4d1e 100644 --- a/platforms/github/client.go +++ b/platforms/github/client.go @@ -22,6 +22,9 @@ type githubClient struct { organizationCacheMux sync.RWMutex organizationNameToIDMap map[string]int64 organizationCache map[int64]*Organization + userCacheMux sync.RWMutex + userNameToIDMap map[string]int64 + userCache map[int64]*User } func NewClient(logger *slog.Logger, config config.GithubClientConfig) (*githubClient, error) { @@ -40,6 +43,8 @@ func NewClient(logger *slog.Logger, config config.GithubClientConfig) (*githubCl organizationNameToIDMap: map[string]int64{}, organizationCache: map[int64]*Organization{}, + userNameToIDMap: map[string]int64{}, + userCache: map[int64]*User{}, } return gitHubClient, nil @@ -57,6 +62,15 @@ func (c *githubClient) FetchRootGroupContent() (map[string]fstree.GroupSource, e rootContent[org.Name] = org } } + + for _, user_name := range c.GithubClientConfig.UserNames { + user, err := c.fetchUser(user_name) + if err != nil { + c.logger.Warn(err.Error()) + } else { + rootContent[user.Name] = user + } + } // TODO: user + current user c.rootContent = rootContent @@ -68,5 +82,8 @@ func (c *githubClient) FetchGroupContent(gid uint64) (map[string]fstree.GroupSou if org, found := c.organizationCache[int64(gid)]; found { return c.fetchOrganizationContent(org) } + if user, found := c.userCache[int64(gid)]; found { + return c.fetchUserContent(user) + } return nil, nil, fmt.Errorf("invalid gid: %v", gid) } diff --git a/platforms/github/organization.go b/platforms/github/organization.go index ffee699..49520f9 100644 --- a/platforms/github/organization.go +++ b/platforms/github/organization.go @@ -62,6 +62,7 @@ func (c *githubClient) fetchOrganization(org_name string) (*Organization, error) // save in cache c.organizationCacheMux.Lock() c.organizationCache[newOrg.ID] = &newOrg + c.organizationNameToIDMap[newOrg.Name] = newOrg.ID c.organizationCacheMux.Unlock() return &newOrg, nil diff --git a/platforms/github/user.go b/platforms/github/user.go index d2e73c2..a2ed503 100644 --- a/platforms/github/user.go +++ b/platforms/github/user.go @@ -1 +1,105 @@ package github + +import ( + "context" + "fmt" + "sync" + + "github.com/badjware/gitlabfs/fstree" + "github.com/google/go-github/v63/github" +) + +type User struct { + ID int64 + Name string + + mux sync.Mutex + + // hold user content + childRepositories map[string]fstree.RepositorySource +} + +func (u *User) GetGroupID() uint64 { + return uint64(u.ID) +} + +func (u *User) InvalidateContentCache() { + u.mux.Lock() + defer u.mux.Unlock() + + // clear child repositories from cache + u.childRepositories = nil +} + +func (c *githubClient) fetchUser(user_name string) (*User, error) { + c.userCacheMux.RLock() + cachedId, found := c.userNameToIDMap[user_name] + if found { + cachedUser := c.userCache[cachedId] + c.userCacheMux.RUnlock() + + // if found in cache, return the cached reference + c.logger.Debug("User cache hit", "user_name", user_name) + return cachedUser, nil + } else { + c.userCacheMux.RUnlock() + + c.logger.Debug("User cache miss", "user_name", user_name) + } + + // If not found in cache, fetch user infos from API + githubUser, _, err := c.client.Users.Get(context.Background(), user_name) + if err != nil { + return nil, fmt.Errorf("failed to fetch user with name %v: %v", user_name, err) + } + newUser := User{ + ID: *githubUser.ID, + Name: *githubUser.Login, + + childRepositories: nil, + } + + // save in cache + c.userCacheMux.Lock() + c.userCache[newUser.ID] = &newUser + c.userNameToIDMap[newUser.Name] = newUser.ID + c.userCacheMux.Unlock() + + return &newUser, nil +} + +func (c *githubClient) fetchUserContent(user *User) (map[string]fstree.GroupSource, map[string]fstree.RepositorySource, error) { + user.mux.Lock() + defer user.mux.Unlock() + + // Get cached data if available + // TODO: cache cache invalidation? + if user.childRepositories == nil { + childRepositories := make(map[string]fstree.RepositorySource) + + // Fetch the user repositories + repositoryListOpt := &github.RepositoryListByUserOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + } + for { + githubRepositories, response, err := c.client.Repositories.ListByUser(context.Background(), user.Name, repositoryListOpt) + if err != nil { + return nil, nil, fmt.Errorf("failed to fetch repository in github: %v", err) + } + for _, githubRepository := range githubRepositories { + repository := c.newRepositoryFromGithubRepository(githubRepository) + if repository != nil { + childRepositories[repository.Path] = repository + } + } + if response.NextPage == 0 { + break + } + // Get the next page + repositoryListOpt.Page = response.NextPage + } + + user.childRepositories = childRepositories + } + return make(map[string]fstree.GroupSource), user.childRepositories, nil +}