Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add contributors page gf-250 #334

Merged
merged 33 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f5d9de6
feat: add contributors table gf-181
sandrvvu Sep 5, 2024
7461829
feat: add git emails table gf-181
sandrvvu Sep 5, 2024
4d0e9b8
feat: add activity logs table gf-181
sandrvvu Sep 5, 2024
2289706
feat: activity log module template gf-181
sandrvvu Sep 7, 2024
22e6216
Merge branch 'main' into 181-feat-save-and-retrieve-activity
sandrvvu Sep 7, 2024
659e7f5
feat: attachedcontributor data to get method response gf-181
sandrvvu Sep 7, 2024
02234ce
feat: added contributors page gf-250
Vitaliistf Sep 10, 2024
a653cba
fix: exceptions message format gf-230
sandrvvu Sep 10, 2024
e2ea3e5
fix: exceptions message format gf-181
sandrvvu Sep 10, 2024
15cf55d
fix: add unsigned for commits number gf-181
sandrvvu Sep 10, 2024
9d0ef9e
feat: separating contributors and git emails modules gf-181
sandrvvu Sep 11, 2024
412a653
Merge branch 'main' into 181-feat-save-and-retrieve-activity
sandrvvu Sep 11, 2024
aa352f5
Merge branch '181-feat-save-and-retrieve-activity' into 250-feat-add-…
Vitaliistf Sep 11, 2024
89c6bc0
feat: added and changed types for contributors gf-250
Vitaliistf Sep 16, 2024
7f20e8c
feat: added helpers and types for contributors table gf-250
Vitaliistf Sep 16, 2024
d00ce32
feat: added store for contributors table gf-250
Vitaliistf Sep 16, 2024
012f5b4
fix: added contributors to store itself gf-250
Vitaliistf Sep 16, 2024
8784427
feat: added contributors retrieving to contributors page gf-250
Vitaliistf Sep 16, 2024
dbaa681
feat: added contributors' projects retrieving methods gf-250
Vitaliistf Sep 16, 2024
bed8389
feat: updated service and repo gf-250
Vitaliistf Sep 16, 2024
cca9651
feat: added controller for contributors gf-250
Vitaliistf Sep 16, 2024
98f4be6
Merge branch 'main' into 250-feat-add-contributors-page
Vitaliistf Sep 16, 2024
77bf018
Merge branch 'main' into 250-feat-add-contributors-page
Vitaliistf Sep 16, 2024
2eb91d1
fix: fixed linting error gf-250
Vitaliistf Sep 16, 2024
e3fd7cb
fix: fixed linting error gf-250
Vitaliistf Sep 16, 2024
1bd48f1
Merge branch 'main' into 250-feat-add-contributors-page
Vitaliistf Sep 16, 2024
1362f3e
Merge branch 'main' into 250-feat-add-contributors-page
Vitaliistf Sep 16, 2024
55ade8b
refactor: renamed methods for getting projects by contributor gf-250
Vitaliistf Sep 17, 2024
8cd5d9f
refactor: made a single query for retrieving contributors with projec…
Vitaliistf Sep 17, 2024
702150d
Merge branch 'main' into 250-feat-add-contributors-page
Vitaliistf Sep 17, 2024
05436d8
refactor: removed projectService dependency from contributorService g…
Vitaliistf Sep 17, 2024
35c8304
refactor: left gitEmails retrieving using with graph fetched gf-250
Vitaliistf Sep 17, 2024
8fab5d3
Merge branch 'main' into 250-feat-add-contributors-page
Vitaliistf Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { database } from "~/libs/modules/database/database.js";
import { logger } from "~/libs/modules/logger/logger.js";
import { activityLogController } from "~/modules/activity-logs/activity-logs.js";
import { authController } from "~/modules/auth/auth.js";
import { contributorController } from "~/modules/contributors/contributors.js";
import { groupController } from "~/modules/groups/groups.js";
import { permissionController } from "~/modules/permissions/permissions.js";
import { projectApiKeyController } from "~/modules/project-api-keys/project-api-keys.js";
Expand All @@ -25,6 +26,7 @@ const apiV1 = new BaseServerApplicationApi(
...projectGroupController.routes,
...projectPermissionsController.routes,
...projectController.routes,
...contributorController.routes,
...userController.routes,
...groupController.routes,
...projectApiKeyController.routes,
Expand Down
86 changes: 86 additions & 0 deletions apps/backend/src/modules/contributors/contributor.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { APIPath } from "~/libs/enums/enums.js";
import {
type APIHandlerResponse,
BaseController,
} from "~/libs/modules/controller/controller.js";
import { HTTPCode } from "~/libs/modules/http/http.js";
import { type Logger } from "~/libs/modules/logger/logger.js";

import { type ContributorService } from "./contributor.service.js";
import { ContributorsApiPath } from "./libs/enums/enums.js";

/**
* @swagger
* components:
* schemas:
* Contributor:
* type: object
* properties:
* id:
* type: number
* minimum: 1
* name:
* type: string
* gitEmails:
* type: array
* items:
* type: object
* properties:
* id:
* type: number
* minimum: 1
* email:
* type: string
* projects:
* type: array
* items:
* type: object
* properties:
* id:
* type: number
* minimum: 1
* name:
* type: string
*/
class ContributorController extends BaseController {
private contributorService: ContributorService;

public constructor(logger: Logger, contributorService: ContributorService) {
super(logger, APIPath.CONTRIBUTORS);

this.contributorService = contributorService;

this.addRoute({
handler: () => this.findAll(),
method: "GET",
path: ContributorsApiPath.ROOT,
});
}

/**
* @swagger
* /contributors:
* get:
* description: Returns an array of contributors
* responses:
* 200:
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* items:
* type: array
* items:
* $ref: "#/components/schemas/Contributor"
*/
private async findAll(): Promise<APIHandlerResponse> {
return {
payload: await this.contributorService.findAll(),
status: HTTPCode.OK,
};
}
}

export { ContributorController };
41 changes: 38 additions & 3 deletions apps/backend/src/modules/contributors/contributor.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,82 @@ import { type Entity } from "~/libs/types/types.js";
import { type ContributorGetAllItemResponseDto } from "./libs/types/types.js";

class ContributorEntity implements Entity {
private gitEmails: { email: string; id: number }[];
private id: null | number;

private name: string;
private projects: { id: number; name: string }[];

private constructor({ id, name }: { id: null | number; name: string }) {
private constructor({
gitEmails,
id,
name,
projects,
}: {
gitEmails: { email: string; id: number }[];
id: null | number;
name: string;
projects: { id: number; name: string }[];
}) {
this.id = id;
this.name = name;
this.gitEmails = gitEmails;
this.projects = projects;
}

public static initialize({
gitEmails,
id,
name,
projects,
}: {
gitEmails: { email: string; id: number }[];
id: number;
name: string;
projects: { id: number; name: string }[];
}): ContributorEntity {
return new ContributorEntity({
gitEmails,
id,
name,
projects,
});
}

public static initializeNew({ name }: { name: string }): ContributorEntity {
public static initializeNew({
gitEmails = [],
name,
projects = [],
}: {
gitEmails?: { email: string; id: number }[];
name: string;
projects?: { id: number; name: string }[];
}): ContributorEntity {
return new ContributorEntity({
gitEmails,
id: null,
name,
projects,
});
}

public toNewObject(): {
gitEmails: { email: string; id: number }[];
name: string;
projects: { id: number; name: string }[];
} {
return {
gitEmails: this.gitEmails,
name: this.name,
projects: this.projects,
};
}

public toObject(): ContributorGetAllItemResponseDto {
return {
gitEmails: this.gitEmails,
id: this.id as number,
name: this.name,
projects: this.projects,
};
}
}
Expand Down
4 changes: 4 additions & 0 deletions apps/backend/src/modules/contributors/contributor.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import {
} from "~/libs/modules/database/database.js";
import { GitEmailModel } from "~/modules/git-emails/git-emails.js";

import { type ProjectModel } from "../projects/project.model.js";

class ContributorModel extends AbstractModel {
public gitEmails!: GitEmailModel[];
public name!: string;
public projects!: ProjectModel[];

public static override get relationMappings(): RelationMappings {
return {
Expand Down
49 changes: 39 additions & 10 deletions apps/backend/src/modules/contributors/contributor.repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { raw } from "objection";

import { type Repository } from "~/libs/types/types.js";

import { ContributorEntity } from "./contributor.entity.js";
Expand All @@ -15,9 +17,7 @@ class ContributorRepository implements Repository {

const contributor = await this.contributorModel
.query()
.insert({
name,
})
.insert({ name })
.execute();

return ContributorEntity.initialize(contributor);
Expand All @@ -28,22 +28,51 @@ class ContributorRepository implements Repository {
}

public async find(id: number): Promise<ContributorEntity | null> {
const item = await this.contributorModel.query().findById(id).execute();
const contributor = await this.contributorModel
.query()
.findById(id)
.withGraphFetched("gitEmails");
GvoFor marked this conversation as resolved.
Show resolved Hide resolved

return item ? ContributorEntity.initialize(item) : null;
if (!contributor) {
return null;
}

return ContributorEntity.initialize(contributor);
}

public findAll(): ReturnType<Repository["findAll"]> {
return Promise.resolve({ items: [] });
public async findAll(): Promise<{ items: ContributorEntity[] }> {
const contributorsWithProjectsAndEmails = await this.contributorModel
.query()
.select("contributors.*")
.select(
raw(
"COALESCE(ARRAY_AGG(DISTINCT jsonb_build_object('id', projects.id, 'name', projects.name)) FILTER (WHERE projects.id IS NOT NULL), '{}') AS projects",
),
)
.leftJoin("git_emails", "contributors.id", "git_emails.contributor_id")
.leftJoin("activity_logs", "git_emails.id", "activity_logs.git_email_id")
.leftJoin("projects", "activity_logs.project_id", "projects.id")
.groupBy("contributors.id")
.withGraphFetched("gitEmails");

return {
items: contributorsWithProjectsAndEmails.map((contributor) => {
return ContributorEntity.initialize(contributor);
}),
};
}

public async findByName(name: string): Promise<ContributorEntity | null> {
const item = await this.contributorModel
const contributor = await this.contributorModel
.query()
.findOne({ name })
.execute();
.withGraphFetched("gitEmails");

return item ? ContributorEntity.initialize(item) : null;
if (!contributor) {
return null;
}

return ContributorEntity.initialize(contributor);
}

public update(): ReturnType<Repository["update"]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ContributorError } from "./libs/exceptions/exceptions.js";
import {
type ContributorCreateRequestDto,
type ContributorGetAllItemResponseDto,
type ContributorGetAllResponseDto,
} from "./libs/types/types.js";

class ContributorService implements Service {
Expand Down Expand Up @@ -48,16 +49,34 @@ class ContributorService implements Service {
return item.toObject();
}

public findAll(): ReturnType<Service["findAll"]> {
return Promise.resolve({ items: [] });
public async findAll(): Promise<ContributorGetAllResponseDto> {
const contributors = await this.contributorRepository.findAll();

return {
items: contributors.items.map((item) => {
const contributor = item.toObject();

return {
...contributor,
gitEmails: contributor.gitEmails.map((gitEmail) => ({
email: gitEmail.email,
id: gitEmail.id,
})),
};
}),
};
}

public async findByName(
name: string,
): Promise<ContributorGetAllItemResponseDto | null> {
const item = await this.contributorRepository.findByName(name);

return item ? item.toObject() : null;
if (!item) {
return null;
}

return item.toObject();
}

public update(): ReturnType<Service["update"]> {
Expand Down
14 changes: 10 additions & 4 deletions apps/backend/src/modules/contributors/contributors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { logger } from "~/libs/modules/logger/logger.js";

import { ContributorController } from "./contributor.controller.js";
import { ContributorModel } from "./contributor.model.js";
import { ContributorRepository } from "./contributor.repository.js";
import { ContributorService } from "./contributors.service.js";
import { ContributorService } from "./contributor.service.js";

const contributorRepository = new ContributorRepository(ContributorModel);
const contributorService = new ContributorService(contributorRepository);
const contributorController = new ContributorController(
logger,
contributorService,
);

export { ContributorModel } from "./contributor.model.js";
export { ContributorRepository } from "./contributor.repository.js";
export { ContributorService } from "./contributors.service.js";
export { contributorService };
export { ContributorService } from "./contributor.service.js";
export { contributorController, contributorService };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ContributorsApiPath } from "@git-fit/shared";
9 changes: 9 additions & 0 deletions apps/frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { store } from "~/libs/modules/store/store.js";
import { AccessManagement } from "~/pages/access-management/access-management.jsx";
import { Analytics } from "~/pages/analytics/analytics.jsx";
import { Auth } from "~/pages/auth/auth.jsx";
import { Contributors } from "~/pages/contributors/contributors.jsx";
import { NoAccess } from "~/pages/no-access/no-access.jsx";
import { NotFound } from "~/pages/not-found/not-found.jsx";
import { Profile } from "~/pages/profile/profile.jsx";
Expand Down Expand Up @@ -57,6 +58,14 @@ createRoot(document.querySelector("#root") as HTMLElement).render(
),
path: AppRoute.PROFILE,
},
{
element: (
<ProtectedRoute>
<Contributors />
</ProtectedRoute>
),
path: AppRoute.CONTRIBUTORS,
},
{
element: (
<ProtectedRoute>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const SIDEBAR_ITEMS: NavigationItem[] = [
label: "Access Management",
pagePermissions: [PermissionKey.MANAGE_USER_ACCESS],
},
{
href: AppRoute.CONTRIBUTORS,
icon: "contributors",
label: "Contributors",
},
{
href: AppRoute.ANALYTICS,
icon: "analytics",
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/libs/enums/app-route.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const AppRoute = {
ACCESS_MANAGEMENT: "/access-management",
ANALYTICS: "/analytics",
ANY: "*",
CONTRIBUTORS: "/contributors",
NO_ACCESS: "/no-access",
PROFILE: "/profile",
PROJECT: "/projects/:id",
Expand Down
Loading