@@ -17,19 +17,28 @@ export type GitHubRelease = Awaited<
1717 ReturnType < InstanceType < typeof Octokit > [ "rest" ] [ "repos" ] [ "listReleases" ] >
1818> [ "data" ] [ number ] ;
1919
20- type KeyType = "releases" | "descriptions" | "issue" | "pr" ;
20+ export type Member = Awaited <
21+ ReturnType < InstanceType < typeof Octokit > [ "rest" ] [ "orgs" ] [ "listMembers" ] >
22+ > [ "data" ] [ number ] ;
23+
24+ type OwnerKeyType = "members" ;
25+
26+ type RepoKeyType = "releases" | "descriptions" | "issue" | "issues" | "pr" | "prs" ;
2127
2228export type ItemDetails = {
2329 comments : Awaited < ReturnType < Issues [ "listComments" ] > > [ "data" ] ;
2430} ;
2531
32+ export type Issue = Awaited < ReturnType < Issues [ "get" ] > > [ "data" ] ;
2633export type IssueDetails = ItemDetails & {
27- info : Awaited < ReturnType < Issues [ "get" ] > > [ "data" ] ;
34+ info : Issue ;
2835 linkedPrs : LinkedItem [ ] ;
2936} ;
3037
38+ export type PullRequest = Awaited < ReturnType < Pulls [ "get" ] > > [ "data" ] ;
39+ export type ListedPullRequest = Awaited < ReturnType < Pulls [ "list" ] > > [ "data" ] [ number ] ;
3140export type PullRequestDetails = ItemDetails & {
32- info : Awaited < ReturnType < Pulls [ "get" ] > > [ "data" ] ;
41+ info : PullRequest ;
3342 commits : Awaited < ReturnType < Pulls [ "listCommits" ] > > [ "data" ] ;
3443 files : Awaited < ReturnType < Pulls [ "listFiles" ] > > [ "data" ] ;
3544 linkedIssues : LinkedItem [ ] ;
@@ -70,6 +79,10 @@ const FULL_DETAILS_TTL = 60 * 60 * 2; // 2 hours
7079 * The TTL of the cached descriptions, in seconds.
7180 */
7281const DESCRIPTIONS_TTL = 60 * 60 * 24 * 10 ; // 10 days
82+ /**
83+ * The TTL of organization members, in seconds.
84+ */
85+ const MEMBERS_TTL = 60 * 60 * 24 * 2 ; // 2 days
7386
7487/**
7588 * A fetch layer to reach the GitHub API
@@ -98,6 +111,22 @@ export class GitHubCache {
98111 } ) ;
99112 }
100113
114+ /**
115+ * Generates a Redis key from the passed info.
116+ *
117+ * @param owner the GitHub repository owner
118+ * @param type the kind of cache to use
119+ * @param args the optional additional values to append
120+ * at the end of the key; every element will be interpolated
121+ * in a string
122+ * @returns the pure computed key
123+ * @private
124+ */
125+ #getOwnerKey( owner : string , type : OwnerKeyType , ...args : unknown [ ] ) {
126+ const strArgs = args . map ( a => `:${ a } ` ) ;
127+ return `owner:${ owner } :${ type } ${ strArgs } ` ;
128+ }
129+
101130 /**
102131 * Generates a Redis key from the passed info.
103132 *
@@ -110,7 +139,7 @@ export class GitHubCache {
110139 * @returns the pure computed key
111140 * @private
112141 */
113- #getRepoKey( owner : string , repo : string , type : KeyType , ...args : unknown [ ] ) {
142+ #getRepoKey( owner : string , repo : string , type : RepoKeyType , ...args : unknown [ ] ) {
114143 const strArgs = args . map ( a => `:${ a } ` ) ;
115144 return `repo:${ owner } /${ repo } :${ type } ${ strArgs } ` ;
116145 }
@@ -130,7 +159,7 @@ export class GitHubCache {
130159 owner : string ,
131160 repo : string ,
132161 id : number ,
133- type : ExtractStrict < KeyType , "issue" | "pr" > | undefined = undefined
162+ type : ExtractStrict < RepoKeyType , "issue" | "pr" > | undefined = undefined
134163 ) {
135164 // Known type we assume the existence of
136165 switch ( type ) {
@@ -575,6 +604,115 @@ export class GitHubCache {
575604 return Object . fromEntries ( descriptions ) ;
576605 }
577606
607+ /**
608+ * Get the list of members for a given organization.
609+ *
610+ * @param owner the GitHub organization name
611+ * @returns a list of members, or `undefined` if not existing
612+ */
613+ async getOrganizationMembers ( owner : string ) : Promise < Member [ ] | undefined > {
614+ const cacheKey = this . #getOwnerKey( owner , "members" ) ;
615+
616+ const cachedMembers = await this . #redis. json . get < Member [ ] > ( cacheKey ) ;
617+ if ( cachedMembers ) {
618+ console . log ( `Cache hit for members of ${ owner } ` ) ;
619+ return cachedMembers . length ? cachedMembers : undefined ; // technically we can have a real empty list, but we ignore this case here
620+ }
621+
622+ console . log ( `Cache miss for members of ${ owner } , fetching from GitHub API` ) ;
623+
624+ try {
625+ const { data : members } = await this . #octokit. rest . orgs . listPublicMembers ( {
626+ org : owner ,
627+ per_page
628+ } ) ;
629+
630+ await this . #redis. json . set ( cacheKey , "$" , members ) ;
631+ await this . #redis. expire ( cacheKey , MEMBERS_TTL ) ;
632+
633+ return members ;
634+ } catch {
635+ await this . #redis. json . set ( cacheKey , "$" , [ ] ) ;
636+ await this . #redis. expire ( cacheKey , MEMBERS_TTL ) ;
637+
638+ return undefined ;
639+ }
640+ }
641+
642+ /**
643+ * Get all the issues for a given GitHub repository.
644+ *
645+ * @param owner the GitHub repository owner
646+ * @param repo the GitHub repository name
647+ * @returns a list of issues, or `undefined` if not existing
648+ */
649+ async getAllIssues ( owner : string , repo : string ) : Promise < Issue [ ] | undefined > {
650+ const cacheKey = this . #getRepoKey( owner , repo , "issues" ) ;
651+
652+ const cachedIssues = await this . #redis. json . get < Issue [ ] > ( cacheKey ) ;
653+ if ( cachedIssues ) {
654+ console . log ( `Cache hit for issues for ${ owner } ` ) ;
655+ return cachedIssues . length ? cachedIssues : undefined ;
656+ }
657+
658+ console . log ( `Cache miss for issues for ${ owner } ` ) ;
659+
660+ try {
661+ const { data : issues } = await this . #octokit. rest . issues . listForRepo ( {
662+ owner,
663+ repo,
664+ per_page
665+ } ) ;
666+
667+ await this . #redis. json . set ( cacheKey , "$" , issues ) ;
668+ await this . #redis. expire ( cacheKey , FULL_DETAILS_TTL ) ;
669+
670+ return issues ;
671+ } catch {
672+ await this . #redis. json . set ( cacheKey , "$" , [ ] ) ;
673+ await this . #redis. expire ( cacheKey , FULL_DETAILS_TTL ) ;
674+
675+ return undefined ;
676+ }
677+ }
678+
679+ /**
680+ * Get all the pull requests for a given GitHub repository.
681+ *
682+ * @param owner the GitHub repository owner
683+ * @param repo the GitHub repository name
684+ * @returns a list of pull requests, or `undefined` if not existing
685+ */
686+ async getAllPRs ( owner : string , repo : string ) : Promise < ListedPullRequest [ ] | undefined > {
687+ const cacheKey = this . #getRepoKey( owner , repo , "prs" ) ;
688+
689+ const cachedPrs = await this . #redis. json . get < ListedPullRequest [ ] > ( cacheKey ) ;
690+ if ( cachedPrs ) {
691+ console . log ( `Cache hit for PRs for ${ owner } ` ) ;
692+ return cachedPrs . length ? cachedPrs : undefined ;
693+ }
694+
695+ console . log ( `Cache miss for PRs for PRs for ${ owner } ` ) ;
696+
697+ try {
698+ const { data : prs } = await this . #octokit. rest . pulls . list ( {
699+ owner,
700+ repo,
701+ per_page
702+ } ) ;
703+
704+ await this . #redis. json . set ( cacheKey , "$" , prs ) ;
705+ await this . #redis. expire ( cacheKey , FULL_DETAILS_TTL ) ;
706+
707+ return prs ;
708+ } catch {
709+ await this . #redis. json . set ( cacheKey , "$" , [ ] ) ;
710+ await this . #redis. expire ( cacheKey , FULL_DETAILS_TTL ) ;
711+
712+ return undefined ;
713+ }
714+ }
715+
578716 /**
579717 * Checks if releases are present in the cache for the
580718 * given GitHub info
@@ -586,7 +724,7 @@ export class GitHubCache {
586724 * @param type the kind of cache to target
587725 * @returns whether the repository is cached or not
588726 */
589- async exists ( owner : string , repo : string , type : KeyType ) {
727+ async exists ( owner : string , repo : string , type : RepoKeyType ) {
590728 const cacheKey = this . #getRepoKey( owner , repo , type ) ;
591729 const result = await this . #redis. exists ( cacheKey ) ;
592730 return result === 1 ;
@@ -601,7 +739,7 @@ export class GitHubCache {
601739 * from the cache
602740 * @param type the kind of cache to target
603741 */
604- async deleteEntry ( owner : string , repo : string , type : KeyType ) {
742+ async deleteEntry ( owner : string , repo : string , type : RepoKeyType ) {
605743 const cacheKey = this . #getRepoKey( owner , repo , type ) ;
606744 await this . #redis. del ( cacheKey ) ;
607745 }
0 commit comments