Skip to content

Commit 5d57781

Browse files
committed
Add Bitbucket service
1 parent 131e053 commit 5d57781

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

api/src/bitbucket/service.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { ConfigService } from "src/config/service";
2+
import { FetchService } from "src/fetch/service";
3+
import { Service } from "typedi";
4+
5+
import {
6+
BitbucketRepositoryContributor,
7+
BitbucketUser,
8+
GetRepositoryInput,
9+
GetRepositoryResponse,
10+
ListRepositoryContributorsResponse,
11+
} from "./types";
12+
13+
@Service()
14+
export class BitbucketService {
15+
constructor(
16+
private readonly configService: ConfigService,
17+
private readonly fetchService: FetchService,
18+
) {}
19+
20+
public getRepository = async ({
21+
owner,
22+
repo,
23+
}: GetRepositoryInput): Promise<GetRepositoryResponse> => {
24+
const repoInfo = await this.fetchService.get<GetRepositoryResponse>(
25+
`${this.apiURL}/repositories/${owner}/${repo}`,
26+
{ headers: this.bitbucketToken ? { Authorization: `Token ${this.bitbucketToken}` } : {} },
27+
);
28+
29+
return repoInfo;
30+
};
31+
32+
public listRepositoryContributors = async ({
33+
owner,
34+
repo,
35+
}: GetRepositoryInput): Promise<ListRepositoryContributorsResponse> => {
36+
interface RepoCommitsResponse {
37+
values: Array<{ author: { user?: BitbucketUser } }>;
38+
next?: string;
39+
}
40+
41+
const contributorsRecord: Record<string, BitbucketRepositoryContributor> = {};
42+
43+
let url: string | null = `${this.apiURL}/repositories/${owner}/${repo}/commits`;
44+
45+
while (url) {
46+
const commits: RepoCommitsResponse = await this.fetchService.get<RepoCommitsResponse>(url, {
47+
headers: this.bitbucketToken ? { Authorization: `Token ${this.bitbucketToken}` } : {},
48+
});
49+
50+
for (const commit of commits.values) {
51+
if (!commit.author.user) continue;
52+
53+
const author = commit.author.user;
54+
if (!contributorsRecord[author.uuid]) {
55+
contributorsRecord[author.uuid] = { ...author, contributions: 0 };
56+
}
57+
contributorsRecord[author.uuid].contributions += 1;
58+
}
59+
60+
url = commits.next || null;
61+
}
62+
63+
const contributors = Object.values(contributorsRecord);
64+
65+
// @TODO-ZM: validate responses using DTOs, for all fetchService methods
66+
if (!Array.isArray(contributors)) return [];
67+
68+
return contributors;
69+
};
70+
71+
private bitbucketToken = this.configService.env().BITBUCKET_TOKEN;
72+
private apiURL = "https://api.bitbucket.org/2.0";
73+
}

api/src/bitbucket/types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// import { GeneralResponse } from "src/app/types";
2+
3+
export interface BitbucketUser {
4+
uuid: string;
5+
username?: string;
6+
display_name: string;
7+
nickname?: string;
8+
links: { avatar: { href: "https://bitbucket.org/account/open-listings/avatar/" } };
9+
type: "user" | "team";
10+
}
11+
12+
export interface BitbucketRepositoryContributor extends BitbucketUser {
13+
contributions: number;
14+
}
15+
16+
export type ListRepositoryContributorsResponse = BitbucketRepositoryContributor[];
17+
18+
export interface GetRepositoryInput {
19+
owner: string;
20+
repo: string;
21+
}
22+
23+
export interface GetRepositoryResponse {
24+
slug: string;
25+
name: string;
26+
owner: BitbucketUser;
27+
}

api/src/config/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ export class EnvRecord {
4343

4444
@IsString()
4545
OPENAI_KEY = "no-key";
46+
47+
@IsString()
48+
@IsOptional()
49+
BITBUCKET_TOKEN?: string;
4650
}

0 commit comments

Comments
 (0)