@valentine-efagene/qshelter-common 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/OpenApiHelper.ts +121 -0
- package/decorator/permission.decorator.ts +4 -0
- package/decorator/tenant.decorator.ts +16 -0
- package/dist/entities/BaseEntity.d.ts +11 -0
- package/dist/entities/BaseEntity.js +54 -0
- package/dist/entities/BaseEntity.js.map +1 -0
- package/dist/entities/TenantAwareEntity.d.ts +10 -0
- package/dist/entities/TenantAwareEntity.js +52 -0
- package/dist/entities/TenantAwareEntity.js.map +1 -0
- package/dist/entities/TenantAwareRepository.d.ts +13 -0
- package/dist/entities/TenantAwareRepository.js +65 -0
- package/dist/entities/TenantAwareRepository.js.map +1 -0
- package/dist/entities/amenity.entity.d.ts +4 -0
- package/dist/entities/amenity.entity.js +27 -0
- package/dist/entities/amenity.entity.js.map +1 -0
- package/dist/entities/common.entity.d.ts +17 -0
- package/dist/entities/common.entity.js +63 -0
- package/dist/entities/common.entity.js.map +1 -0
- package/dist/entities/common.pure.entity.d.ts +11 -0
- package/dist/entities/common.pure.entity.js +51 -0
- package/dist/entities/common.pure.entity.js.map +1 -0
- package/dist/entities/index.d.ts +27 -0
- package/dist/entities/index.js +44 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/mortgage-document.entity.d.ts +14 -0
- package/dist/entities/mortgage-document.entity.js +58 -0
- package/dist/entities/mortgage-document.entity.js.map +1 -0
- package/dist/entities/mortgage-downpayment-installment.entity.d.ts +19 -0
- package/dist/entities/mortgage-downpayment-installment.entity.js +63 -0
- package/dist/entities/mortgage-downpayment-installment.entity.js.map +1 -0
- package/dist/entities/mortgage-downpayment-payment.entity.d.ts +28 -0
- package/dist/entities/mortgage-downpayment-payment.entity.js +84 -0
- package/dist/entities/mortgage-downpayment-payment.entity.js.map +1 -0
- package/dist/entities/mortgage-downpayment.entity.d.ts +22 -0
- package/dist/entities/mortgage-downpayment.entity.js +66 -0
- package/dist/entities/mortgage-downpayment.entity.js.map +1 -0
- package/dist/entities/mortgage-step.entity.d.ts +12 -0
- package/dist/entities/mortgage-step.entity.js +52 -0
- package/dist/entities/mortgage-step.entity.js.map +1 -0
- package/dist/entities/mortgage-type.entity.d.ts +10 -0
- package/dist/entities/mortgage-type.entity.js +46 -0
- package/dist/entities/mortgage-type.entity.js.map +1 -0
- package/dist/entities/mortgage.entity.d.ts +37 -0
- package/dist/entities/mortgage.entity.js +124 -0
- package/dist/entities/mortgage.entity.js.map +1 -0
- package/dist/entities/password_reset_tokens.entity.d.ts +7 -0
- package/dist/entities/password_reset_tokens.entity.js +43 -0
- package/dist/entities/password_reset_tokens.entity.js.map +1 -0
- package/dist/entities/permission.entity.d.ts +6 -0
- package/dist/entities/permission.entity.js +30 -0
- package/dist/entities/permission.entity.js.map +1 -0
- package/dist/entities/property-document.entity.d.ts +6 -0
- package/dist/entities/property-document.entity.js +34 -0
- package/dist/entities/property-document.entity.js.map +1 -0
- package/dist/entities/property-media.entity.d.ts +6 -0
- package/dist/entities/property-media.entity.js +36 -0
- package/dist/entities/property-media.entity.js.map +1 -0
- package/dist/entities/property.entity.d.ts +36 -0
- package/dist/entities/property.entity.js +182 -0
- package/dist/entities/property.entity.js.map +1 -0
- package/dist/entities/refresh_token.entity.d.ts +7 -0
- package/dist/entities/refresh_token.entity.js +35 -0
- package/dist/entities/refresh_token.entity.js.map +1 -0
- package/dist/entities/role.entity.d.ts +8 -0
- package/dist/entities/role.entity.js +39 -0
- package/dist/entities/role.entity.js.map +1 -0
- package/dist/entities/settings.entity.d.ts +17 -0
- package/dist/entities/settings.entity.js +79 -0
- package/dist/entities/settings.entity.js.map +1 -0
- package/dist/entities/social.entity.d.ts +8 -0
- package/dist/entities/social.entity.js +46 -0
- package/dist/entities/social.entity.js.map +1 -0
- package/dist/entities/tenant.entity.d.ts +29 -0
- package/dist/entities/tenant.entity.js +82 -0
- package/dist/entities/tenant.entity.js.map +1 -0
- package/dist/entities/transaction.entity.d.ts +17 -0
- package/dist/entities/transaction.entity.js +84 -0
- package/dist/entities/transaction.entity.js.map +1 -0
- package/dist/entities/user.entity.d.ts +26 -0
- package/dist/entities/user.entity.js +103 -0
- package/dist/entities/user.entity.js.map +1 -0
- package/dist/entities/user_suspensions.entity.d.ts +7 -0
- package/dist/entities/user_suspensions.entity.js +42 -0
- package/dist/entities/user_suspensions.entity.js.map +1 -0
- package/dist/entities/wallet.entity.d.ts +17 -0
- package/dist/entities/wallet.entity.js +79 -0
- package/dist/entities/wallet.entity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination/index.d.ts +2 -0
- package/dist/pagination/index.js +19 -0
- package/dist/pagination/index.js.map +1 -0
- package/dist/pagination/pagination.helper.d.ts +7 -0
- package/dist/pagination/pagination.helper.js +40 -0
- package/dist/pagination/pagination.helper.js.map +1 -0
- package/dist/pagination/pagination.types.d.ts +19 -0
- package/dist/pagination/pagination.types.js +3 -0
- package/dist/pagination/pagination.types.js.map +1 -0
- package/dist/standard-response.d.ts +7 -0
- package/dist/standard-response.js +27 -0
- package/dist/standard-response.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/common.type.d.ts +26 -0
- package/dist/types/common.type.js +26 -0
- package/dist/types/common.type.js.map +1 -0
- package/dist/types/mortgage-fsm.types.d.ts +180 -0
- package/dist/types/mortgage-fsm.types.js +130 -0
- package/dist/types/mortgage-fsm.types.js.map +1 -0
- package/dist/types/policy.types.d.ts +18 -0
- package/dist/types/policy.types.js +3 -0
- package/dist/types/policy.types.js.map +1 -0
- package/dist/types/property.type.d.ts +9 -0
- package/dist/types/property.type.js +15 -0
- package/dist/types/property.type.js.map +1 -0
- package/dist/types/social.enums.d.ts +15 -0
- package/dist/types/social.enums.js +22 -0
- package/dist/types/social.enums.js.map +1 -0
- package/dist/types/tenant.enums.d.ts +13 -0
- package/dist/types/tenant.enums.js +19 -0
- package/dist/types/tenant.enums.js.map +1 -0
- package/dist/types/transaction.type.d.ts +8 -0
- package/dist/types/transaction.type.js +14 -0
- package/dist/types/transaction.type.js.map +1 -0
- package/dist/types/user.enums.d.ts +10 -0
- package/dist/types/user.enums.js +16 -0
- package/dist/types/user.enums.js.map +1 -0
- package/entities/BaseEntity.ts +34 -0
- package/entities/TenantAwareEntity.ts +34 -0
- package/entities/TenantAwareRepository.ts +100 -0
- package/entities/amenity.entity.ts +10 -0
- package/entities/common.entity.ts +46 -0
- package/entities/common.pure.entity.ts +36 -0
- package/entities/index.ts +27 -0
- package/entities/mortgage-document.entity.ts +37 -0
- package/entities/mortgage-downpayment-installment.entity.ts +40 -0
- package/entities/mortgage-downpayment-payment.entity.ts +61 -0
- package/entities/mortgage-downpayment.entity.ts +43 -0
- package/entities/mortgage-step.entity.ts +33 -0
- package/entities/mortgage-type.entity.ts +31 -0
- package/entities/mortgage.entity.ts +89 -0
- package/entities/password_reset_tokens.entity.ts +25 -0
- package/entities/permission.entity.ts +12 -0
- package/entities/property-document.entity.ts +21 -0
- package/entities/property-media.entity.ts +23 -0
- package/entities/property.entity.ts +147 -0
- package/entities/refresh_token.entity.ts +16 -0
- package/entities/role.entity.ts +20 -0
- package/entities/settings.entity.ts +56 -0
- package/entities/social.entity.ts +27 -0
- package/entities/tenant.entity.ts +65 -0
- package/entities/transaction.entity.ts +56 -0
- package/entities/user.entity.ts +89 -0
- package/entities/user_suspensions.entity.ts +24 -0
- package/entities/wallet.entity.ts +54 -0
- package/guard/permission.guard.ts +42 -0
- package/guard/swagger-auth.guard.ts +9 -0
- package/helpers/ArrayHelper.ts +1 -0
- package/helpers/ConstantHelper.ts +101 -0
- package/helpers/CustomNamingStrategy.ts +27 -0
- package/helpers/DateHelper.ts +21 -0
- package/helpers/EmailHelper.ts +38 -0
- package/helpers/FileSystemHelper.ts +101 -0
- package/index.ts +9 -0
- package/middleware/TenantMiddleware.ts +52 -0
- package/package.json +46 -0
- package/pagination/index.ts +2 -0
- package/pagination/pagination.helper.ts +57 -0
- package/pagination/pagination.types.ts +21 -0
- package/standard-response.ts +16 -0
- package/tsconfig.json +33 -0
- package/types/common.type.ts +32 -0
- package/types/mortgage-fsm.types.ts +279 -0
- package/types/policy.types.ts +21 -0
- package/types/property.type.ts +10 -0
- package/types/social.enums.ts +17 -0
- package/types/tenant.enums.ts +14 -0
- package/types/transaction.type.ts +9 -0
- package/types/user.enums.ts +11 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
class ConstantHelper {
|
|
2
|
+
public static readonly mailConstants = {
|
|
3
|
+
// Emails
|
|
4
|
+
firstEmail: process.env.FIRST_EMAIL,
|
|
5
|
+
secondEmail: process.env.INFO_EMAIL,
|
|
6
|
+
thirdEmail: process.env.SUPPORT_EMAIL,
|
|
7
|
+
supportEmail: process.env.SUPPORT_EMAIL,
|
|
8
|
+
|
|
9
|
+
// Website & Branding
|
|
10
|
+
appLink: process.env.APP_LINK,
|
|
11
|
+
appLogo: process.env.APP_LOGO,
|
|
12
|
+
|
|
13
|
+
// Phone Numbers
|
|
14
|
+
firstPhoneNumber: process.env.FIRST_PHONE_NUMBER,
|
|
15
|
+
secondPhoneNumber: process.env.SECOND_PHONE_NUMBER,
|
|
16
|
+
firstPhoneLink: process.env.FIRST_PHONE_LINK,
|
|
17
|
+
secondPhoneLink: process.env.SECOND_PHONE_LINK,
|
|
18
|
+
supportPhone: process.env.SUPPORT_PHONE,
|
|
19
|
+
|
|
20
|
+
// Office Addresses
|
|
21
|
+
firstOfficeAddress: process.env.FIRST_OFFICE_ADDRESS,
|
|
22
|
+
secondOfficeAddress: process.env.SECOND_OFFICE_ADDRESS,
|
|
23
|
+
officeAddress: process.env.OFFICE_ADDRESS,
|
|
24
|
+
|
|
25
|
+
// Social Media
|
|
26
|
+
facebookLink: process.env.FACEBOOK_LINK,
|
|
27
|
+
facebookLogo: process.env.FACEBOOK_LOGO,
|
|
28
|
+
twitterLink: process.env.TWITTER_LINK,
|
|
29
|
+
twitterLogo: process.env.TWITTER_LOGO,
|
|
30
|
+
instagramLink: process.env.INSTAGRAM_LINK,
|
|
31
|
+
instagramLogo: process.env.INSTAGRAM_LOGO,
|
|
32
|
+
youtubeLink: process.env.YOUTUBE_LINK,
|
|
33
|
+
youtubeLogo: process.env.YOUTUBE_LOGO,
|
|
34
|
+
linkedinLink: process.env.LINKEDIN_LINK,
|
|
35
|
+
linkedinLogo: process.env.LINKEDIN_LOGO,
|
|
36
|
+
|
|
37
|
+
// Other
|
|
38
|
+
unsubscribeLink: process.env.UNSUBSCRIBE_LINK,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
public static get socialLinks() {
|
|
42
|
+
const { mailConstants: c } = this;
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
{ name: "Facebook", link: c.facebookLink, icon: c.facebookLogo },
|
|
46
|
+
{ name: "Twitter", link: c.twitterLink, icon: c.twitterLogo },
|
|
47
|
+
{ name: "Instagram", link: c.instagramLink, icon: c.instagramLogo },
|
|
48
|
+
{ name: "YouTube", link: c.youtubeLink, icon: c.youtubeLogo },
|
|
49
|
+
{ name: "LinkedIn", link: c.linkedinLink, icon: c.linkedinLogo },
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static readonly exampleTicketTemplate = `
|
|
54
|
+
<!DOCTYPE html>
|
|
55
|
+
<html lang="en">
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="UTF-8" />
|
|
58
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
59
|
+
<title>Your Ticket</title>
|
|
60
|
+
</head>
|
|
61
|
+
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background: #f7f7f7;">
|
|
62
|
+
<table width="100%" cellpadding="0" cellspacing="0" style="padding: 2rem 0;">
|
|
63
|
+
<tr>
|
|
64
|
+
<td align="center">
|
|
65
|
+
<table width="400" cellpadding="0" cellspacing="0" style="background: #fff; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); overflow: hidden;">
|
|
66
|
+
<tr>
|
|
67
|
+
<td style="padding: 1.5rem; background: #2c3e50; color: #fff; text-align: center;">
|
|
68
|
+
<h2 style="margin: 0; font-size: 24px;">Your Event Ticket</h2>
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
|
|
72
|
+
<tr>
|
|
73
|
+
<td style="padding: 1.5rem;">
|
|
74
|
+
<p style="margin: 0 0 1rem;">Hello <strong>{{name}}</strong>,</p>
|
|
75
|
+
<p style="margin: 0 0 1rem;">Thanks for registering! Below is your ticket barcode:</p>
|
|
76
|
+
|
|
77
|
+
<div style="text-align: center; margin: 2rem 0;">
|
|
78
|
+
<img src="{{qrCodeUrl}}" alt="Ticket Barcode" style="width: 80%; max-width: 150px;" />
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<p style="font-size: 0.9rem; color: #7f8c8d; text-align: center;">
|
|
82
|
+
Please bring this ticket or show it on your phone at the entrance.
|
|
83
|
+
</p>
|
|
84
|
+
</td>
|
|
85
|
+
</tr>
|
|
86
|
+
|
|
87
|
+
<tr>
|
|
88
|
+
<td style="padding: 1rem; background: #ecf0f1; text-align: center;">
|
|
89
|
+
<small style="color: #95a5a6;">© 2025 MediaCraft. All rights reserved.</small>
|
|
90
|
+
</td>
|
|
91
|
+
</tr>
|
|
92
|
+
</table>
|
|
93
|
+
</td>
|
|
94
|
+
</tr>
|
|
95
|
+
</table>
|
|
96
|
+
</body>
|
|
97
|
+
</html>
|
|
98
|
+
`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { ConstantHelper }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class CustomNamingStrategy
|
|
4
|
+
extends DefaultNamingStrategy
|
|
5
|
+
implements NamingStrategyInterface {
|
|
6
|
+
tableName(className: string, customName: string): string {
|
|
7
|
+
return customName ? customName : this.snakeCase(className);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
columnName(
|
|
11
|
+
propertyName: string,
|
|
12
|
+
customName: string,
|
|
13
|
+
//embeddedPrefixes: string[],
|
|
14
|
+
): string {
|
|
15
|
+
return customName ? customName : this.snakeCase(propertyName);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
relationName(propertyName: string): string {
|
|
19
|
+
return this.snakeCase(propertyName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private snakeCase(name: string): string {
|
|
23
|
+
return name
|
|
24
|
+
.replace(/([A-Z])/g, (match) => `_${match.toLowerCase()}`)
|
|
25
|
+
.replace(/^_/, '');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
export default class DateHelper {
|
|
4
|
+
public static formatDateToYYYYMMDD(dateString: string) {
|
|
5
|
+
try {
|
|
6
|
+
const date = new Date(dateString);
|
|
7
|
+
|
|
8
|
+
// Ensure the input is a valid Date object
|
|
9
|
+
if (!(date instanceof Date)) {
|
|
10
|
+
throw new BadRequestException('Invalid date');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Format the date using the formatter
|
|
14
|
+
const formattedDate = date.toISOString().split('T')[0];
|
|
15
|
+
|
|
16
|
+
return formattedDate;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new BadRequestException('Invalid date');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import * as Handlebars from 'handlebars';
|
|
3
|
+
import { FileSystemHelper } from "./FileSystemHelper";
|
|
4
|
+
|
|
5
|
+
export default class EmailHelper {
|
|
6
|
+
public static async compileStringTemplate(rawTemplate: string, variables: Record<string, any>): Promise<string> {
|
|
7
|
+
try {
|
|
8
|
+
// Compile it with Handlebars
|
|
9
|
+
const compiled = Handlebars.compile(rawTemplate);
|
|
10
|
+
|
|
11
|
+
const html = compiled(variables);
|
|
12
|
+
|
|
13
|
+
return html;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(`Failed to generate HTML: ${error.message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static async compileTemplate(templatePath: string, variables: Record<string, any>): Promise<string> {
|
|
20
|
+
try {
|
|
21
|
+
// Load the raw template
|
|
22
|
+
const rawTemplate = FileSystemHelper.loadFile(templatePath);
|
|
23
|
+
|
|
24
|
+
// Compile it with Handlebars
|
|
25
|
+
const compiled = Handlebars.compile(rawTemplate);
|
|
26
|
+
|
|
27
|
+
if (templatePath.includes('layout')) {
|
|
28
|
+
console.log({ compiled, variables })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const html = compiled(variables);
|
|
32
|
+
|
|
33
|
+
return html;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`Failed to generate HTML: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync, promises, constants } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export class FileSystemHelper {
|
|
5
|
+
private static root: string = path.join(__dirname, '../../mail/templates/');
|
|
6
|
+
|
|
7
|
+
public static async getFilePath(fileName: string): Promise<string> {
|
|
8
|
+
const filePath = path.join(this.root, fileName);
|
|
9
|
+
return filePath
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public static async checkFileExists(fileName: string): Promise<boolean> {
|
|
13
|
+
const filePath = path.join(this.root, fileName);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
await promises.access(`${filePath}`, constants.F_OK)
|
|
17
|
+
console.log({ filePath, found: true })
|
|
18
|
+
return true
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.log({ filePath, found: false })
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static loadFileWithFullPath(path: string): string {
|
|
26
|
+
const fileContent = readFileSync(path, 'utf-8');
|
|
27
|
+
return fileContent;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static loadFile(fileName: string): string {
|
|
31
|
+
const filePath = path.join(this.root, fileName);
|
|
32
|
+
|
|
33
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
34
|
+
return fileContent;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static async splitFilePath(filePath: string) {
|
|
38
|
+
// Normalize the file path to handle both Windows and Unix formats
|
|
39
|
+
let normalizedPath = path.normalize(filePath)
|
|
40
|
+
normalizedPath = normalizedPath.replaceAll('\\', '/')
|
|
41
|
+
|
|
42
|
+
// Get the file name (last part of the path)
|
|
43
|
+
const fileName = path.basename(normalizedPath);
|
|
44
|
+
|
|
45
|
+
// Get the directory path
|
|
46
|
+
const directoryPath = path.dirname(normalizedPath);
|
|
47
|
+
const extension = path.extname(fileName)
|
|
48
|
+
|
|
49
|
+
// Split the directory path into an array of folders
|
|
50
|
+
const folderArray = directoryPath.split('/');
|
|
51
|
+
|
|
52
|
+
// Reassemble the folder path as a string (optional, if you need it in this format)
|
|
53
|
+
const folderPath = folderArray.join('/');
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
folderArray,
|
|
57
|
+
folderPath,
|
|
58
|
+
fileName,
|
|
59
|
+
extension
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static loadTemplate(fileName: string): string {
|
|
64
|
+
const filePath = path.join(this.root, `${fileName}`);
|
|
65
|
+
|
|
66
|
+
const fileContent = readFileSync(filePath, 'utf-8');
|
|
67
|
+
return fileContent;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Method to list all .html files from the templates directory
|
|
71
|
+
public static listHtmlFilesInTemplates(): string[] {
|
|
72
|
+
const htmlFiles: string[] = [];
|
|
73
|
+
|
|
74
|
+
const getFilesRecursively = (dir: string) => {
|
|
75
|
+
try {
|
|
76
|
+
const files = readdirSync(dir);
|
|
77
|
+
|
|
78
|
+
files.forEach(file => {
|
|
79
|
+
const fullPath = path.join(dir, file);
|
|
80
|
+
const relativePath = path.relative(this.root, fullPath);
|
|
81
|
+
|
|
82
|
+
if (statSync(fullPath).isDirectory()) {
|
|
83
|
+
// If it's a directory, go deeper
|
|
84
|
+
getFilesRecursively(fullPath);
|
|
85
|
+
} else if (file.endsWith('.html')) {
|
|
86
|
+
// If it's an HTML file, add to the list
|
|
87
|
+
htmlFiles.push(relativePath);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.log(error)
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Start the recursion from the templates folder
|
|
97
|
+
getFilesRecursively(this.root);
|
|
98
|
+
|
|
99
|
+
return htmlFiles;
|
|
100
|
+
}
|
|
101
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Injectable, NestMiddleware } from '@nestjs/common';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
|
|
4
|
+
// Extend Express Request to include tenantId
|
|
5
|
+
declare global {
|
|
6
|
+
namespace Express {
|
|
7
|
+
interface Request {
|
|
8
|
+
tenantId?: number;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class TenantMiddleware implements NestMiddleware {
|
|
15
|
+
async use(req: Request, _res: Response, next: NextFunction) {
|
|
16
|
+
// Prefer explicit header `x-tenant-id`
|
|
17
|
+
const tenantIdHeader = (req.headers['x-tenant-id'] as string) || (req.headers['X-Tenant-Id'] as string);
|
|
18
|
+
if (tenantIdHeader) {
|
|
19
|
+
const id = parseInt(tenantIdHeader, 10);
|
|
20
|
+
if (!isNaN(id)) {
|
|
21
|
+
req.tenantId = id;
|
|
22
|
+
return next();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Fall back to subdomain extraction (subdomain.domain.tld)
|
|
27
|
+
const host = (req.hostname || req.headers.host || '').toString();
|
|
28
|
+
const subdomain = this.extractSubdomain(host);
|
|
29
|
+
if (subdomain) {
|
|
30
|
+
// If subdomain is numeric, treat as tenantId; otherwise leave undefined
|
|
31
|
+
const id = parseInt(subdomain, 10);
|
|
32
|
+
if (!isNaN(id)) {
|
|
33
|
+
req.tenantId = id;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
next();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private extractSubdomain(host: string): string | null {
|
|
41
|
+
if (!host) return null;
|
|
42
|
+
const hostname = host.split(':')[0];
|
|
43
|
+
const parts = hostname.split('.');
|
|
44
|
+
if (hostname === 'localhost' || /^(\d{1,3}\.){3}\d{1,3}$/.test(hostname)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (parts.length >= 3) return parts[0];
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default TenantMiddleware;
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@valentine-efagene/qshelter-common",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared common utilities, DTOs, guards, middleware, decorators, and pagination for QShelter",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"publish:public": "npm publish --access public",
|
|
11
|
+
"build:watch": "tsc -w",
|
|
12
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
13
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"common",
|
|
17
|
+
"utilities",
|
|
18
|
+
"nestjs",
|
|
19
|
+
"entities",
|
|
20
|
+
"pagination",
|
|
21
|
+
"qshelter"
|
|
22
|
+
],
|
|
23
|
+
"author": "Valentine Efagene",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"typeorm": "^0.3.0",
|
|
27
|
+
"@nestjs/common": "^10.0.0",
|
|
28
|
+
"@nestjs/swagger": "^7.4.0",
|
|
29
|
+
"@nestjs/config": "^3.0.0",
|
|
30
|
+
"@nestjs/jwt": "^10.0.0",
|
|
31
|
+
"class-validator": "^0.14.0",
|
|
32
|
+
"class-transformer": "^0.5.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@nestjs/swagger": "^7.4.0",
|
|
37
|
+
"typeorm": "^0.3.20",
|
|
38
|
+
"@nestjs/common": "^10.0.0",
|
|
39
|
+
"@nestjs/config": "^3.0.0",
|
|
40
|
+
"@nestjs/jwt": "^10.0.0",
|
|
41
|
+
"class-validator": "^0.14.0",
|
|
42
|
+
"class-transformer": "^0.5.1",
|
|
43
|
+
"typescript": "^5.0.0",
|
|
44
|
+
"@types/node": "^20.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PaginatedResponse, PaginationQuery } from './pagination.types';
|
|
2
|
+
|
|
3
|
+
export class PaginationHelper {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a paginated response object
|
|
6
|
+
*/
|
|
7
|
+
static paginate<T>(
|
|
8
|
+
items: T[],
|
|
9
|
+
total: number,
|
|
10
|
+
query: PaginationQuery
|
|
11
|
+
): PaginatedResponse<T> {
|
|
12
|
+
const page = Math.max(1, query.page || 1);
|
|
13
|
+
const limit = this.getLimit(query.limit);
|
|
14
|
+
const totalPages = Math.ceil(total / limit);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
data: items,
|
|
18
|
+
meta: {
|
|
19
|
+
total,
|
|
20
|
+
page,
|
|
21
|
+
limit,
|
|
22
|
+
totalPages,
|
|
23
|
+
hasNext: page < totalPages,
|
|
24
|
+
hasPrev: page > 1,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Calculate the number of records to skip for pagination
|
|
31
|
+
*/
|
|
32
|
+
static getSkip(page?: number, limit?: number): number {
|
|
33
|
+
const actualPage = Math.max(1, page || 1);
|
|
34
|
+
const actualLimit = this.getLimit(limit);
|
|
35
|
+
return (actualPage - 1) * actualLimit;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get validated limit (min: 1, max: 100, default: 10)
|
|
40
|
+
*/
|
|
41
|
+
static getLimit(limit?: number): number {
|
|
42
|
+
return Math.min(100, Math.max(1, limit || 10));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse and validate pagination query parameters
|
|
47
|
+
*/
|
|
48
|
+
static parseQuery(query: any): PaginationQuery {
|
|
49
|
+
return {
|
|
50
|
+
page: query.page ? parseInt(query.page, 10) : 1,
|
|
51
|
+
limit: query.limit ? parseInt(query.limit, 10) : 10,
|
|
52
|
+
sortBy: query.sortBy || 'id',
|
|
53
|
+
sortOrder: query.sortOrder?.toUpperCase() === 'ASC' ? 'ASC' : 'DESC',
|
|
54
|
+
search: query.search || undefined,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface PaginationQuery {
|
|
2
|
+
page?: number;
|
|
3
|
+
limit?: number;
|
|
4
|
+
sortBy?: string;
|
|
5
|
+
sortOrder?: 'ASC' | 'DESC';
|
|
6
|
+
search?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PaginationMeta {
|
|
10
|
+
total: number;
|
|
11
|
+
page: number;
|
|
12
|
+
limit: number;
|
|
13
|
+
totalPages: number;
|
|
14
|
+
hasNext: boolean;
|
|
15
|
+
hasPrev: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PaginatedResponse<T> {
|
|
19
|
+
data: T[];
|
|
20
|
+
meta: PaginationMeta;
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ApiExtraModels } from "@nestjs/swagger";
|
|
2
|
+
|
|
3
|
+
@ApiExtraModels(StandardApiResponse)
|
|
4
|
+
export class StandardApiResponse<T = any> {
|
|
5
|
+
ok: boolean
|
|
6
|
+
status: number;
|
|
7
|
+
message: string;
|
|
8
|
+
body?: T;
|
|
9
|
+
|
|
10
|
+
constructor(status: number, message: string, body?: T) {
|
|
11
|
+
this.message = message;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
this.ok = status >= 200 && status < 300;
|
|
15
|
+
}
|
|
16
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "ES2021",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strictNullChecks": false,
|
|
16
|
+
"noImplicitAny": false,
|
|
17
|
+
"strictBindCallApply": false,
|
|
18
|
+
"forceConsistentCasingInFileNames": false,
|
|
19
|
+
"noFallthroughCasesInSwitch": false,
|
|
20
|
+
"esModuleInterop": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"pagination/**/*.ts",
|
|
24
|
+
"entities/**/*.ts",
|
|
25
|
+
"types/**/*.ts",
|
|
26
|
+
"index.ts",
|
|
27
|
+
"standard-response.ts"
|
|
28
|
+
],
|
|
29
|
+
"exclude": [
|
|
30
|
+
"node_modules",
|
|
31
|
+
"dist"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export enum DocumentStatus {
|
|
2
|
+
APPROVED = 'APPROVED',
|
|
3
|
+
PENDING = 'PENDING',
|
|
4
|
+
DECLINED = 'DECLINED',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum Status {
|
|
8
|
+
APPROVED = 'APPROVED',
|
|
9
|
+
PENDING = 'PENDING',
|
|
10
|
+
DECLINED = 'DECLINED',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IDocument {
|
|
14
|
+
url: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class PaginatedData {
|
|
20
|
+
total: number;
|
|
21
|
+
|
|
22
|
+
totalPages: number;
|
|
23
|
+
|
|
24
|
+
currentPage: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export enum Frequency {
|
|
28
|
+
MONTHLY = 'MONTHLY',
|
|
29
|
+
YEARLY = 'YEARLY',
|
|
30
|
+
ONE_TIME = 'ONE_TIME',
|
|
31
|
+
DAILY = 'DAILY',
|
|
32
|
+
}
|