Skip to content

Commit 601fd44

Browse files
committed
feat: projects a contributor helped in
1 parent 4d7db0e commit 601fd44

File tree

5 files changed

+84
-5
lines changed

5 files changed

+84
-5
lines changed

api/src/contributor/controller.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import { Service } from "typedi";
33

44
import { ContributorRepository } from "./repository";
55
import { GetContributorResponse, GetContributorsResponse } from "./types";
6+
import { ProjectRepository } from "src/project/repository";
67

78
@Service()
89
@Controller("/Contributors")
910
export class ContributorController {
10-
constructor(private readonly contributorRepository: ContributorRepository) {}
11+
constructor(
12+
private readonly contributorRepository: ContributorRepository,
13+
private readonly projectRepository: ProjectRepository,
14+
) {}
1115

1216
@Get("/")
1317
public async getContributors(): Promise<GetContributorsResponse> {
@@ -20,15 +24,18 @@ export class ContributorController {
2024

2125
@Get("/:id")
2226
public async getContributor(@Param("id") id: string): Promise<GetContributorResponse> {
23-
const [contributor] = await Promise.all([this.contributorRepository.findWithStats(id)]);
27+
const [contributor, projects] = await Promise.all([
28+
this.contributorRepository.findWithStats(id),
29+
this.projectRepository.findForContributor(id),
30+
]);
2431

2532
if (!contributor) throw new NotFoundError("Contributor not found");
2633

2734
return {
2835
contributor: {
2936
...contributor,
30-
// @TODO-ZM: Add contributions and projects
31-
// projects,
37+
projects,
38+
// @TODO-ZM: Add contributions
3239
// contributions,
3340
},
3441
};

api/src/contributor/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ContributorEntity } from "@dzcode.io/models/dist/contributor";
2+
import { ProjectEntity } from "@dzcode.io/models/dist/project";
23
import { GeneralResponse } from "src/app/types";
34

45
export interface GetContributorsResponse extends GeneralResponse {
@@ -16,5 +17,13 @@ export interface GetContributorResponse extends GeneralResponse {
1617
ranking: number;
1718
totalContributionScore: number;
1819
totalRepositoryCount: number;
20+
projects: Array<
21+
Pick<ProjectEntity, "id" | "name"> & {
22+
totalRepoContributorCount: number;
23+
totalRepoScore: number;
24+
totalRepoStars: number;
25+
ranking: number;
26+
}
27+
>;
1928
};
2029
}

api/src/project/repository.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,47 @@ export class ProjectRepository {
116116
return camelCased;
117117
}
118118

119+
public async findForContributor(id: string) {
120+
const statement = sql`
121+
SELECT
122+
id,
123+
name,
124+
sum(repo_with_stats.contributor_count)::int as total_repo_contributor_count,
125+
sum(repo_with_stats.stars)::int as total_repo_stars,
126+
sum(repo_with_stats.score)::int as total_repo_score,
127+
ROUND( 100 * sum(repo_with_stats.contributor_count) + 100 * sum(repo_with_stats.stars) + max(repo_with_stats.score) - sum(repo_with_stats.score) / sum(repo_with_stats.contributor_count) )::int as ranking
128+
FROM
129+
(
130+
SELECT
131+
repository_id,
132+
project_id,
133+
sum(score) as score,
134+
count(*) as contributor_count,
135+
stars
136+
FROM
137+
${contributorRepositoryRelationTable}
138+
JOIN
139+
${repositoriesTable} ON ${contributorRepositoryRelationTable.repositoryId} = ${repositoriesTable.id}
140+
WHERE
141+
${contributorRepositoryRelationTable.contributorId} = ${id}
142+
GROUP BY
143+
${contributorRepositoryRelationTable.repositoryId}, ${repositoriesTable.projectId}, ${repositoriesTable.stars}
144+
) as repo_with_stats
145+
JOIN
146+
${projectsTable} ON ${projectsTable.id} = repo_with_stats.project_id
147+
GROUP BY
148+
${projectsTable.id}
149+
ORDER BY
150+
ranking DESC
151+
`;
152+
153+
const raw = await this.postgresService.db.execute(statement);
154+
const entries = Array.from(raw);
155+
const unStringifiedRaw = unStringifyDeep(entries);
156+
const camelCased = camelCaseObject(unStringifiedRaw);
157+
return camelCased;
158+
}
159+
119160
public async findForSitemap() {
120161
const statement = sql`
121162
SELECT

web/src/components/locale/dictionary.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ Besides the open tasks on [/Contribute](/Contribute) page, you can also contribu
247247
en: "repositories",
248248
ar: "مستودعات",
249249
},
250-
250+
"contributor-contributed-to-projects": {
251+
en: "Contributed to projects",
252+
ar: "ساهم في المشاريع",
253+
},
251254
"projects-title": {
252255
en: "Browse a growing list of Algerian open-source projects | DzCode i/o",
253256
ar: "تصفح قائمة المشاريع الجزائرية مفتوحة المصدر | DzCode i / o",

web/src/pages/team/contributor/index.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TryAgain } from "src/components/try-again";
99
import { fetchContributorAction } from "src/redux/actions/contributor";
1010
import { useAppDispatch, useAppSelector } from "src/redux/store";
1111
import { getContributorURL } from "src/utils/contributor";
12+
import { getProjectURL } from "src/utils/project";
1213

1314
// ts-prune-ignore-next
1415
export default function Page(): JSX.Element {
@@ -81,6 +82,24 @@ export default function Page(): JSX.Element {
8182
</div>
8283
</div>
8384
</div>
85+
{contributor.projects.length > 0 ? (
86+
<>
87+
<h2 className="text-lg font-bold">
88+
<Locale contributor-contributed-to-projects />
89+
</h2>
90+
<div className="flex flex-row gap-4 flex-wrap">
91+
{contributor.projects.map((project, projectIndex) => (
92+
<Link
93+
key={projectIndex}
94+
href={getProjectURL(project)}
95+
className="card card-compact bg-base-200 rounded-lg p-4 w-full md:w-auto"
96+
>
97+
{project.name}
98+
</Link>
99+
))}
100+
</div>
101+
</>
102+
) : null}
84103
</div>
85104
)}
86105
</div>

0 commit comments

Comments
 (0)