Skip to content

Commit

Permalink
chore: add datatable proper implementation and design with filters
Browse files Browse the repository at this point in the history
  • Loading branch information
brunotot committed Jun 10, 2024
1 parent cfd720a commit 550c1a5
Show file tree
Hide file tree
Showing 67 changed files with 1,831 additions and 190 deletions.
3 changes: 1 addition & 2 deletions .lintstagedrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"**/*.{ts,tsx,js,jsx}": ["prettier --write"],
"**/*.{md,mdx,yml,json}": ["prettier --write"],
"commit-msg": ["node scripts/js/commitEmoji.js"]
"**/*.{md,mdx,yml,json}": ["prettier --write"]
}
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@
"stopOnEntry": false,
"autoAttachChildProcesses": true,
"runtimeVersion": "21.7.0",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["--filter", "backend", "run", "start"],
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "backend:start"],
"presentation": { "group": "3" }
},
{
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ Our goal is to provide an easy setup and deployment process, allowing developers
<td align="right">^7.2.0</td>
<td>Provides rate limiting to protect against brute force attacks</td>
</tr>
<tr>
<td>flatted</td>
<td align="right">^3.3.1</td>
<td>-</td>
</tr>
<tr>
<td>helmet</td>
<td align="right">^7.1.0</td>
Expand Down Expand Up @@ -485,6 +490,11 @@ Our goal is to provide an easy setup and deployment process, allowing developers
<td align="right">^6.22.3</td>
<td>Provides routing functionality for the React frontend application</td>
</tr>
<tr>
<td>react-use</td>
<td align="right">^17.5.0</td>
<td>-</td>
</tr>
</tbody>
</table>

Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"typedoc": "typedoc && pnpm run script:customizeTypedocOutput",
"lint": "npx eslint . --fix",
"backend:build": "pnpm --filter backend run build",
"backend:start": "npm run start --prefix packages/backend",
"script:customizeTypedocOutput": "bash scripts/sh/customizeTypedocOutput.sh",
"script:writeDependenciesMarkdown": "node scripts/js/writeDependenciesMarkdown.js",
"script:writeReadmeMarkdown": "node scripts/js/writeReadmeMarkdown.js",
Expand Down Expand Up @@ -53,5 +54,8 @@
"mongodb",
"express",
"node"
]
],
"dependencies": {
"react-use": "^17.5.0"
}
}
1 change: 1 addition & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"dotenv": "^16.4.5",
"express": "^4.18.2",
"express-rate-limit": "^7.2.0",
"flatted": "^3.3.1",
"helmet": "^7.1.0",
"hpp": "^0.2.3",
"jsonwebtoken": "^9.0.2",
Expand Down
32 changes: 14 additions & 18 deletions packages/backend/src/infrastructure/controllers/UserController.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ErrorResponse, type TODO } from "@org/shared";
import type { MongoSort, RouteInput, RouteOutput } from "@org/backend/types";
import type { RouteInput, RouteOutput } from "@org/backend/types";
import { Autowired, Contract, Injectable } from "@org/backend/decorators";
import { withPaginableParams } from "@org/backend/infrastructure/middleware/locals/withPaginableParams";
import { type UserService } from "@org/backend/infrastructure/service/UserService";

@Injectable("userController")
Expand All @@ -28,25 +27,11 @@ export class UserController {
};
}

@Contract("User.pagination", withPaginableParams())
@Contract("User.pagination")
async pagination({ query }: RouteInput<"User.pagination">): RouteOutput<"User.pagination"> {
//throw new Error("Testing error");
const paginationOptions = {
filters: {},
sort: (query.sort ? query.sort.split(",").map(value => value.split("|")) : []) as MongoSort,
page: query.page,
limit: query.limit,
search: {
fields: ["username", "email"],
regex: query.search,
},
};

const paginatedResult = (await this.userService.search(paginationOptions)) as TODO;

return {
status: 200,
body: paginatedResult,
body: (await this.userService.search(query.paginationOptions)) as TODO,
};
}

Expand All @@ -58,4 +43,15 @@ export class UserController {
body: user,
};
}

@Contract("User.deleteByUsername")
async deleteByUsername({
body,
}: RouteInput<"User.deleteByUsername">): RouteOutput<"User.deleteByUsername"> {
await this.userService.deleteByUsername(body.username);
return {
status: 201,
body: "OK",
};
}
}
2 changes: 0 additions & 2 deletions packages/backend/src/infrastructure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ export * from "./middleware/locals/withJwt";
export * from "./middleware/locals/withRateLimit";
export * from "./middleware/locals/withUserRoles";
export * from "./middleware/locals/withValidatedBody";
export * from "./middleware/locals/withPaginableParams";
export * from "./middleware/globals/index";

/* @org/backend/infrastructure/repository */
export * from "./repository/PaginableRepository";
export * from "./repository/UserRepository";
export * from "./repository/ErrorLogRepository";
export * from "./repository/impl/UserRepositoryImpl";
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { type ZodSchema } from "zod";
import { DatabaseManager } from "@org/backend/config";
import { type PaginableRepository } from "@org/backend/infrastructure/repository/PaginableRepository";
import { type MongoPaginationOptions } from "@org/backend/types";
import { type PaginationOptions } from "@org/shared";
import { type PaginationResult } from "@org/shared";
import * as PaginationUtils from "@org/backend/infrastructure/utils/PaginationUtils";

export abstract class AbstractRepository<T> implements PaginableRepository<T> {
export abstract class AbstractRepository<T> {
private readonly schema: ZodSchema<T>;
private readonly searchFields: string[];

constructor(schema: ZodSchema<T>) {
this.schema = schema;
this.searchFields = this.buildSearch();
}

abstract buildSearch(): string[];

protected get collection() {
return DatabaseManager.getInstance().collection(this.schema);
}

async search(options?: MongoPaginationOptions): Promise<PaginationResult<T>> {
return PaginationUtils.paginate(this.collection, options);
async search(options?: PaginationOptions): Promise<PaginationResult<T>> {
return PaginationUtils.paginate(this.collection, this.searchFields, options);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ErrorLog } from "@org/shared";
import { type PaginableRepository } from "@org/backend/infrastructure/repository/PaginableRepository";
import type { PaginationOptions, PaginationResult, ErrorLog } from "@org/shared";

export interface ErrorLogRepository extends PaginableRepository<ErrorLog> {
export interface ErrorLogRepository {
search: (options: PaginationOptions) => Promise<PaginationResult<ErrorLog>>;
insertOne: (user: Omit<ErrorLog, "_id">) => Promise<ErrorLog>;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type User } from "@org/shared";
import { type PaginableRepository } from "@org/backend/infrastructure/repository/PaginableRepository";
import type { PaginationOptions, PaginationResult, User } from "@org/shared";

export interface UserRepository extends PaginableRepository<User> {
export interface UserRepository {
deleteByUsername(username: string): Promise<void>;
search: (options: PaginationOptions) => Promise<PaginationResult<User>>;
findOneByUsername: (username: string) => Promise<User | null>;
findOneByRefreshTokens: (refreshTokens: string[]) => Promise<User | null>;
findAll: () => Promise<User[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export class ErrorLogRepositoryImpl
extends AbstractRepository<ErrorLog>
implements ErrorLogRepository
{
buildSearch(): string[] {
return [];
}

constructor() {
super(ErrorLog);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { Injectable } from "@org/backend/decorators";
import { User, ObjectId } from "@org/shared";
import { User } from "@org/shared";
import { type UserRepository } from "@org/backend/infrastructure/repository/UserRepository";
import { AbstractRepository } from "../AbstractRepository";

@Injectable("userRepository")
export class UserRepositoryImpl extends AbstractRepository<User> implements UserRepository {
buildSearch(): string[] {
return ["email", "username"];
}

constructor() {
super(User);
}

async deleteByUsername(username: string): Promise<void> {
await this.collection.deleteOne({ username });
}

async findOneByUsername(username: string): Promise<User | null> {
return await this.collection.findOne({ username });
}
Expand All @@ -23,9 +31,8 @@ export class UserRepositoryImpl extends AbstractRepository<User> implements User

//@Transactional()
async insertOne(user: Omit<User, "_id">): Promise<User> {
const candidate = { ...user, _id: new ObjectId() };
const { insertedId } = await this.collection.insertOne(candidate);
return { ...candidate, _id: insertedId };
const { insertedId } = await this.collection.insertOne(user);
return { ...user, _id: insertedId };
}

//@Transactional()
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/infrastructure/service/UserService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type MongoPaginationOptions } from "@org/backend/types";
import { type User, type PaginationResult } from "@org/shared";
import { type User, type PaginationResult, type PaginationOptions } from "@org/shared";

export interface UserService {
search: (options?: MongoPaginationOptions) => Promise<PaginationResult<User>>;
deleteByUsername(username: string): Promise<void>;
search: (params: Partial<PaginationOptions>) => Promise<PaginationResult<User>>;
findAll: () => Promise<User[]>;
create: (user: User) => Promise<User>;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { TODO } from "@org/shared";

import type { PaginationResult } from "@org/shared";
import { PaginationOptions, type TODO } from "@org/shared";
import { type PaginationResult } from "@org/shared";
import { Autowired, Injectable } from "@org/backend/decorators";
import { type MongoPaginationOptions } from "@org/backend/types";
import { type UserRepository } from "@org/backend/infrastructure/repository/UserRepository";
import { type UserService } from "@org/backend/infrastructure/service/UserService";
import { type User } from "@org/shared";
Expand All @@ -11,8 +9,8 @@ import { type User } from "@org/shared";
export class UserServiceImpl implements UserService {
@Autowired() userRepository: UserRepository;

async search(options?: MongoPaginationOptions): Promise<PaginationResult<User>> {
return this.userRepository.search(options);
async search(options: Partial<PaginationOptions>): Promise<PaginationResult<User>> {
return this.userRepository.search(PaginationOptions.parse(options));
}

async findAll(): Promise<User[]> {
Expand All @@ -22,4 +20,8 @@ export class UserServiceImpl implements UserService {
async create(user: User): Promise<User> {
return this.userRepository.insertOne(user) as TODO;
}

async deleteByUsername(username: string): Promise<void> {
return this.userRepository.deleteByUsername(username);
}
}
29 changes: 16 additions & 13 deletions packages/backend/src/infrastructure/utils/PaginationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import type { TODO, PaginationResult } from "@org/shared";
import type { TODO, PaginationResult, PaginationOptions } from "@org/shared";
import type { Collection } from "mongodb";
import type {
MongoFilters,
MongoSearch,
MongoSort,
MongoPaginationOptions,
} from "@org/backend/types";
import type { MongoFilters, MongoSearch, MongoSort } from "@org/backend/types";

export async function paginate(
collection: Collection<TODO>,
options?: MongoPaginationOptions,
searchFields: string[],
options?: PaginationOptions,
): Promise<PaginationResult<TODO>> {
const limit = options?.limit ?? 10;
const limit = options?.rowsPerPage ?? 10;
const page = options?.page ?? 0;
const search = options?.search ?? { fields: [], regex: "" };
const sort = options?.sort ?? [];
const search = options?.search ?? "";
const order = options?.order ?? [];
const filters = options?.filters ?? {};
const skip = page * limit;

const pipeline: TODO[] = [];

pipeline.push(...buildMatchPipeline(search, filters));
pipeline.push(...buildSortPipeline(sort));
pipeline.push(...buildMatchPipeline({ fields: searchFields, regex: search }, filters));
pipeline.push(
...buildSortPipeline(
order.map(s => {
const [field, sortOrder] = s.split(" ");
return [field, sortOrder as "asc" | "desc"];
}) as TODO,
),
);

pipeline.push(
...[
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^14.1.0",
"react-router-dom": "^6.22.3"
"react-router-dom": "^6.22.3",
"react-use": "^17.5.0"
},
"devDependencies": {
"@preact/signals-react-transform": "^0.3.1",
Expand Down
Loading

0 comments on commit 550c1a5

Please sign in to comment.