@duvdu-v1/duvdu 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/custom.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Request } from "express";
2
+ import { IjwtPayload } from "./src/types/JwtPayload";
3
+ declare module "express-serve-static-core" {
4
+ export interface Request {
5
+ user?: IjwtPayload;
6
+ }
7
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@duvdu-v1/duvdu",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "@types/node": "^20.11.0",
14
+ "express": "^4.18.2",
15
+ "express-validator": "^7.0.1",
16
+ "mongoose": "^8.0.4",
17
+ "multer": "^1.4.5-lts.1",
18
+ "node-nats-streaming": "^0.3.2"
19
+ },
20
+ "devDependencies": {
21
+ "@types/express": "^4.17.21",
22
+ "@types/multer": "^1.4.11"
23
+ }
24
+ }
@@ -0,0 +1,12 @@
1
+ import { CustomError } from "./custom-error";
2
+
3
+ export class BadRequestError extends CustomError {
4
+ statusCode: number = 400;
5
+ constructor(message: string) {
6
+ super(message);
7
+ Object.setPrototypeOf(this, BadRequestError.prototype);
8
+ }
9
+ serializeError(): { message: string; field?: string | undefined }[] {
10
+ return [{ message: this.message }];
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ export abstract class CustomError extends Error {
2
+ abstract statusCode: number;
3
+ constructor(message: string) {
4
+ super(message);
5
+ Object.setPrototypeOf(this, CustomError.prototype);
6
+ }
7
+
8
+ abstract serializeError(): { message: string; field?: string }[];
9
+ }
@@ -0,0 +1,12 @@
1
+ import { CustomError } from "./custom-error";
2
+
3
+ export class NotFound extends CustomError {
4
+ statusCode = 404;
5
+ constructor() {
6
+ super("not found error");
7
+ Object.setPrototypeOf(this, NotFound.prototype);
8
+ }
9
+ serializeError(): { message: string; field?: string | undefined }[] {
10
+ return [{ message: "not found error" }];
11
+ }
12
+ }
@@ -0,0 +1,13 @@
1
+ import { CustomError } from "./custom-error";
2
+
3
+ export class UnauthenticatedError extends CustomError {
4
+ statusCode = 401;
5
+ constructor() {
6
+ super("un-authenticated error");
7
+ Object.setPrototypeOf(this, UnauthenticatedError.prototype);
8
+ }
9
+
10
+ serializeError(): { message: string; field?: string | undefined }[] {
11
+ return [{ message: "un-authenticated error" }];
12
+ }
13
+ }
@@ -0,0 +1,13 @@
1
+ import { CustomError } from "./custom-error";
2
+
3
+ export class UnauthorizedError extends CustomError {
4
+ statusCode = 403;
5
+ constructor() {
6
+ super("un-unauthorized error");
7
+ Object.setPrototypeOf(this, UnauthorizedError.prototype);
8
+ }
9
+
10
+ serializeError(): { message: string; field?: string | undefined }[] {
11
+ return [{ message: "un-unauthorized error" }];
12
+ }
13
+ }
@@ -0,0 +1,16 @@
1
+ import { CustomError } from "./custom-error";
2
+ import { ValidationError as vError } from "express-validator";
3
+
4
+ export class ValidationError extends CustomError {
5
+ statusCode = 422;
6
+ constructor(public error: vError[]) {
7
+ super("validation error");
8
+ Object.setPrototypeOf(this, ValidationError.prototype);
9
+ }
10
+ serializeError(): { message: string; field?: string | undefined }[] {
11
+ return this.error.map((el) => {
12
+ if (el.type === "field") return { message: el.msg, field: el.path };
13
+ return { message: el.msg };
14
+ });
15
+ }
16
+ }
@@ -0,0 +1,44 @@
1
+ import { Stan, Message } from "node-nats-streaming";
2
+ import { Subject } from "./subject";
3
+
4
+ interface Event {
5
+ subject: Subject;
6
+ data: any;
7
+ }
8
+
9
+ export abstract class Lisener<T extends Event> {
10
+ abstract queueGroupName: string;
11
+ abstract subject: T["subject"];
12
+ abstract onMessage(data: T["data"], msg: Message): void;
13
+ protected client: Stan;
14
+ protected ackWait = 5 * 1000;
15
+ constructor(client: Stan) {
16
+ this.client = client;
17
+ }
18
+ subscriptionOptions() {
19
+ return this.client
20
+ .subscriptionOptions()
21
+ .setDeliverAllAvailable()
22
+ .setManualAckMode(true)
23
+ .setAckWait(this.ackWait)
24
+ .setDurableName(this.queueGroupName);
25
+ }
26
+
27
+ listen() {
28
+ const subscription = this.client.subscribe(
29
+ this.subject,
30
+ this.queueGroupName,
31
+ this.subscriptionOptions()
32
+ );
33
+ subscription.on("message", (msg: Message) => {
34
+ console.log(`Message received: ${this.subject} / ${this.queueGroupName}`);
35
+ const parsedData = this.parseMessage(msg);
36
+ this.onMessage(parsedData, msg);
37
+ });
38
+ }
39
+
40
+ parseMessage(msg: Message) {
41
+ const data = msg.getData();
42
+ return typeof data === "string" ? JSON.parse(data) : JSON.parse(data.toString("utf-8"));
43
+ }
44
+ }
@@ -0,0 +1,25 @@
1
+ import { Stan } from "node-nats-streaming";
2
+ import { Subject } from "./subject";
3
+
4
+ interface Event {
5
+ subject: Subject;
6
+ data: any;
7
+ }
8
+
9
+ export abstract class Publisher<T extends Event> {
10
+ abstract subject: T["subject"];
11
+ protected client: Stan;
12
+
13
+ constructor(client: Stan) {
14
+ this.client = client;
15
+ }
16
+
17
+ publish(data: T["data"]): Promise<void> {
18
+ return new Promise((resolve, reject) => {
19
+ this.client.publish(this.subject, JSON.stringify(data), (err) => {
20
+ if (err) reject(err);
21
+ resolve();
22
+ });
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ export enum Subject {
2
+ orderCreated = "order:created",
3
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export * from "./events/base-listener";
2
+ export * from "./events/base-publisher";
3
+ export * from "./events/subject";
4
+ export * from "./errors/bad-request-error";
5
+ // export * from "./errors/custom-error";
6
+ export * from "./errors/notfound-error";
7
+ export * from "./errors/unauthenticated-error";
8
+ export * from "./errors/unauthorized-error";
9
+ export * from "./errors/validation-error";
10
+ export * from "./middlewares/global-error-handling.middleware";
11
+ // export * from "./types/Pagination";
12
+ // export * from "./types/UrlQuery";
13
+ export * from "./utils/api-feature";
@@ -0,0 +1,25 @@
1
+ import { ErrorRequestHandler } from "express";
2
+ import { CustomError } from "../errors/custom-error";
3
+ import { MulterError } from "multer";
4
+ export const globalErrorHandlingMiddleware: ErrorRequestHandler = (err, req, res, next) => {
5
+ // custom error
6
+ if (err instanceof CustomError) {
7
+ return res.status(err.statusCode).json({ errors: err.serializeError() });
8
+ }
9
+ // mongo dublicate error
10
+ if (err.name === "MongoServerError" && err.code == "11000")
11
+ return res
12
+ .status(422)
13
+ .json({ errors: [{ message: `${Object.keys(err.keyPattern)} is already exists` }] });
14
+ // unhandled multer error
15
+ if (err instanceof MulterError)
16
+ return res.status(400).json({ errors: [{ message: `${err.field} is invalid` }] });
17
+ // JWT invalid token
18
+ if (err.name === "JsonWebTokenError")
19
+ return res.status(401).json({ errors: [{ message: "invalid token" }] });
20
+ // JWT expired token
21
+ if (err.name === "TokenExpiredError")
22
+ return res.status(401).json({ errors: [{ message: "expired token" }] });
23
+ // unHandled error
24
+ res.status(500).json({ errors: [{ message: "server error" }] });
25
+ };
@@ -0,0 +1,3 @@
1
+ export interface IjwtPayload {
2
+ id: string;
3
+ }
@@ -0,0 +1,7 @@
1
+ export interface Ipagination {
2
+ currentPage: number;
3
+ limit: number;
4
+ numberOfPages: number;
5
+ nextPage: number;
6
+ previousPage: number;
7
+ }
@@ -0,0 +1,9 @@
1
+ export interface IurlQuery {
2
+ limit?: string;
3
+ page?: string;
4
+ sort?: string;
5
+ fields?: string;
6
+ keyword?: string;
7
+ price?: string;
8
+ ratingAverage?: string;
9
+ }
@@ -0,0 +1,27 @@
1
+ import { Types } from "mongoose";
2
+
3
+ export interface Iuser {
4
+ id: string;
5
+ googleId?: string;
6
+ appleId?: string;
7
+ name?: string;
8
+ phoneNumber: { key: string; number: string };
9
+ username: string;
10
+ password?: string;
11
+ verificationCode?: { code: string; expireAt: Date };
12
+ token?: string;
13
+ profileImage?: string;
14
+ coverImage?: string;
15
+ location?: { lat: number; lng: number };
16
+ categroy?: Types.ObjectId;
17
+ acceptedProjectsCounter: number;
18
+ profileViews: number;
19
+ about?: string;
20
+ isOnline: boolean;
21
+ isAvaliableToInstantProjects: boolean;
22
+ pricePerHour?: number;
23
+ plan: Types.ObjectId;
24
+ hasVerificationPadge: boolean;
25
+ avaliableContracts: number;
26
+ rate: { ratersCounter: number; totalRates: number };
27
+ }
@@ -0,0 +1,12 @@
1
+ import { IjwtPayload } from "./../JwtPayload";
2
+ import { RequestHandler, Request, Response } from "express";
3
+ import { Iuser } from "./User";
4
+
5
+ // param, res, req, query
6
+ export interface IsigninHandler
7
+ extends RequestHandler<
8
+ undefined,
9
+ { token: string },
10
+ Partial<Pick<Iuser, "username" | "phoneNumber" | "password">>,
11
+ undefined
12
+ > {}
@@ -0,0 +1,89 @@
1
+ import { Model, Query, Document } from "mongoose";
2
+ import { Ipagination } from "../types/Pagination";
3
+ import { IurlQuery } from "../types/UrlQuery";
4
+
5
+ export class Api_Feature {
6
+ mongooseQuery: Query<Document[], Document, Model<Document>>;
7
+ queryString: IurlQuery;
8
+ filterQuery: Record<string, any>;
9
+ paginateResult: Partial<Ipagination>;
10
+
11
+ constructor(
12
+ mongooseQuery: Query<Document[], Document, Model<Document>>,
13
+ queryString: Partial<IurlQuery>
14
+ ) {
15
+ this.mongooseQuery = mongooseQuery;
16
+ this.queryString = queryString;
17
+ this.paginateResult = {};
18
+ this.filterQuery = {};
19
+ }
20
+
21
+ filter(): this {
22
+ const queryValues: any = { ...this.queryString };
23
+ const expectedQuery: string[] = ["limit", "fields", "page", "keyword", "sort"];
24
+ expectedQuery.forEach((val) => delete queryValues[val]);
25
+
26
+ let queryStr = JSON.stringify(queryValues);
27
+ queryStr = queryStr.replace(/\b(gte|ge|eq|lt|lte)\b/g, (match) => `$${match}`);
28
+
29
+ if (this.queryString.keyword) {
30
+ this.filterQuery = JSON.parse(queryStr);
31
+ } else {
32
+ this.mongooseQuery = this.mongooseQuery.find(JSON.parse(queryStr));
33
+ }
34
+ return this;
35
+ }
36
+
37
+ sort(): this {
38
+ if (this.queryString.sort) {
39
+ const sortBy = this.queryString.sort.split(",").join(" ");
40
+ this.mongooseQuery = this.mongooseQuery.sort(sortBy);
41
+ } else {
42
+ this.mongooseQuery = this.mongooseQuery.sort("-createdAt");
43
+ }
44
+ return this;
45
+ }
46
+
47
+ limitFields(): this {
48
+ if (this.queryString.fields) {
49
+ const fields = this.queryString.fields.split(",").join(" ");
50
+ this.mongooseQuery = this.mongooseQuery.select(fields);
51
+ } else {
52
+ this.mongooseQuery = this.mongooseQuery.select("-__v");
53
+ }
54
+ return this;
55
+ }
56
+
57
+ search(fieldName: string): this {
58
+ // search by keyword by name
59
+ if (this.queryString.keyword) {
60
+ this.filterQuery[fieldName] = { $regex: this.queryString.keyword, $options: "i" };
61
+ this.mongooseQuery = this.mongooseQuery.find(this.filterQuery);
62
+ }
63
+
64
+ return this;
65
+ }
66
+
67
+ pagination(documentCount: number): this {
68
+ const page: number = +(this.queryString.page || 1);
69
+ const limit: number = +(this.queryString.limit || 10);
70
+ const skip: number = (page - 1) * limit;
71
+ const endPageIndex: number = page * limit;
72
+
73
+ const pagination: Partial<Ipagination> = {};
74
+ pagination.currentPage = page;
75
+ pagination.limit = limit;
76
+ pagination.numberOfPages = Math.ceil(documentCount / limit);
77
+ if (endPageIndex < documentCount) {
78
+ pagination.nextPage = page + 1;
79
+ }
80
+
81
+ if (skip > 0) {
82
+ pagination.previousPage = page - 1;
83
+ }
84
+
85
+ this.mongooseQuery = this.mongooseQuery.skip(skip).limit(limit);
86
+ this.paginateResult = pagination;
87
+ return this;
88
+ }
89
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2016",
4
+ "module": "commonjs",
5
+ "rootDir": "./src",
6
+ "declaration": true,
7
+ "outDir": "./build",
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*.ts", "custom.d.ts"]
14
+ }