@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 +7 -0
- package/package.json +24 -0
- package/src/errors/bad-request-error.ts +12 -0
- package/src/errors/custom-error.ts +9 -0
- package/src/errors/notfound-error.ts +12 -0
- package/src/errors/unauthenticated-error.ts +13 -0
- package/src/errors/unauthorized-error.ts +13 -0
- package/src/errors/validation-error.ts +16 -0
- package/src/events/base-listener.ts +44 -0
- package/src/events/base-publisher.ts +25 -0
- package/src/events/subject.ts +3 -0
- package/src/index.ts +13 -0
- package/src/middlewares/global-error-handling.middleware.ts +25 -0
- package/src/types/JwtPayload.ts +3 -0
- package/src/types/Pagination.ts +7 -0
- package/src/types/UrlQuery.ts +9 -0
- package/src/types/auth/User.ts +27 -0
- package/src/types/auth/endpoints.ts +12 -0
- package/src/utils/api-feature.ts +89 -0
- package/tsconfig.json +14 -0
package/custom.d.ts
ADDED
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,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
|
+
}
|
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,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
|
+
}
|