@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.
Files changed (179) hide show
  1. package/OpenApiHelper.ts +121 -0
  2. package/decorator/permission.decorator.ts +4 -0
  3. package/decorator/tenant.decorator.ts +16 -0
  4. package/dist/entities/BaseEntity.d.ts +11 -0
  5. package/dist/entities/BaseEntity.js +54 -0
  6. package/dist/entities/BaseEntity.js.map +1 -0
  7. package/dist/entities/TenantAwareEntity.d.ts +10 -0
  8. package/dist/entities/TenantAwareEntity.js +52 -0
  9. package/dist/entities/TenantAwareEntity.js.map +1 -0
  10. package/dist/entities/TenantAwareRepository.d.ts +13 -0
  11. package/dist/entities/TenantAwareRepository.js +65 -0
  12. package/dist/entities/TenantAwareRepository.js.map +1 -0
  13. package/dist/entities/amenity.entity.d.ts +4 -0
  14. package/dist/entities/amenity.entity.js +27 -0
  15. package/dist/entities/amenity.entity.js.map +1 -0
  16. package/dist/entities/common.entity.d.ts +17 -0
  17. package/dist/entities/common.entity.js +63 -0
  18. package/dist/entities/common.entity.js.map +1 -0
  19. package/dist/entities/common.pure.entity.d.ts +11 -0
  20. package/dist/entities/common.pure.entity.js +51 -0
  21. package/dist/entities/common.pure.entity.js.map +1 -0
  22. package/dist/entities/index.d.ts +27 -0
  23. package/dist/entities/index.js +44 -0
  24. package/dist/entities/index.js.map +1 -0
  25. package/dist/entities/mortgage-document.entity.d.ts +14 -0
  26. package/dist/entities/mortgage-document.entity.js +58 -0
  27. package/dist/entities/mortgage-document.entity.js.map +1 -0
  28. package/dist/entities/mortgage-downpayment-installment.entity.d.ts +19 -0
  29. package/dist/entities/mortgage-downpayment-installment.entity.js +63 -0
  30. package/dist/entities/mortgage-downpayment-installment.entity.js.map +1 -0
  31. package/dist/entities/mortgage-downpayment-payment.entity.d.ts +28 -0
  32. package/dist/entities/mortgage-downpayment-payment.entity.js +84 -0
  33. package/dist/entities/mortgage-downpayment-payment.entity.js.map +1 -0
  34. package/dist/entities/mortgage-downpayment.entity.d.ts +22 -0
  35. package/dist/entities/mortgage-downpayment.entity.js +66 -0
  36. package/dist/entities/mortgage-downpayment.entity.js.map +1 -0
  37. package/dist/entities/mortgage-step.entity.d.ts +12 -0
  38. package/dist/entities/mortgage-step.entity.js +52 -0
  39. package/dist/entities/mortgage-step.entity.js.map +1 -0
  40. package/dist/entities/mortgage-type.entity.d.ts +10 -0
  41. package/dist/entities/mortgage-type.entity.js +46 -0
  42. package/dist/entities/mortgage-type.entity.js.map +1 -0
  43. package/dist/entities/mortgage.entity.d.ts +37 -0
  44. package/dist/entities/mortgage.entity.js +124 -0
  45. package/dist/entities/mortgage.entity.js.map +1 -0
  46. package/dist/entities/password_reset_tokens.entity.d.ts +7 -0
  47. package/dist/entities/password_reset_tokens.entity.js +43 -0
  48. package/dist/entities/password_reset_tokens.entity.js.map +1 -0
  49. package/dist/entities/permission.entity.d.ts +6 -0
  50. package/dist/entities/permission.entity.js +30 -0
  51. package/dist/entities/permission.entity.js.map +1 -0
  52. package/dist/entities/property-document.entity.d.ts +6 -0
  53. package/dist/entities/property-document.entity.js +34 -0
  54. package/dist/entities/property-document.entity.js.map +1 -0
  55. package/dist/entities/property-media.entity.d.ts +6 -0
  56. package/dist/entities/property-media.entity.js +36 -0
  57. package/dist/entities/property-media.entity.js.map +1 -0
  58. package/dist/entities/property.entity.d.ts +36 -0
  59. package/dist/entities/property.entity.js +182 -0
  60. package/dist/entities/property.entity.js.map +1 -0
  61. package/dist/entities/refresh_token.entity.d.ts +7 -0
  62. package/dist/entities/refresh_token.entity.js +35 -0
  63. package/dist/entities/refresh_token.entity.js.map +1 -0
  64. package/dist/entities/role.entity.d.ts +8 -0
  65. package/dist/entities/role.entity.js +39 -0
  66. package/dist/entities/role.entity.js.map +1 -0
  67. package/dist/entities/settings.entity.d.ts +17 -0
  68. package/dist/entities/settings.entity.js +79 -0
  69. package/dist/entities/settings.entity.js.map +1 -0
  70. package/dist/entities/social.entity.d.ts +8 -0
  71. package/dist/entities/social.entity.js +46 -0
  72. package/dist/entities/social.entity.js.map +1 -0
  73. package/dist/entities/tenant.entity.d.ts +29 -0
  74. package/dist/entities/tenant.entity.js +82 -0
  75. package/dist/entities/tenant.entity.js.map +1 -0
  76. package/dist/entities/transaction.entity.d.ts +17 -0
  77. package/dist/entities/transaction.entity.js +84 -0
  78. package/dist/entities/transaction.entity.js.map +1 -0
  79. package/dist/entities/user.entity.d.ts +26 -0
  80. package/dist/entities/user.entity.js +103 -0
  81. package/dist/entities/user.entity.js.map +1 -0
  82. package/dist/entities/user_suspensions.entity.d.ts +7 -0
  83. package/dist/entities/user_suspensions.entity.js +42 -0
  84. package/dist/entities/user_suspensions.entity.js.map +1 -0
  85. package/dist/entities/wallet.entity.d.ts +17 -0
  86. package/dist/entities/wallet.entity.js +79 -0
  87. package/dist/entities/wallet.entity.js.map +1 -0
  88. package/dist/index.d.ts +3 -0
  89. package/dist/index.js +20 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/pagination/index.d.ts +2 -0
  92. package/dist/pagination/index.js +19 -0
  93. package/dist/pagination/index.js.map +1 -0
  94. package/dist/pagination/pagination.helper.d.ts +7 -0
  95. package/dist/pagination/pagination.helper.js +40 -0
  96. package/dist/pagination/pagination.helper.js.map +1 -0
  97. package/dist/pagination/pagination.types.d.ts +19 -0
  98. package/dist/pagination/pagination.types.js +3 -0
  99. package/dist/pagination/pagination.types.js.map +1 -0
  100. package/dist/standard-response.d.ts +7 -0
  101. package/dist/standard-response.js +27 -0
  102. package/dist/standard-response.js.map +1 -0
  103. package/dist/tsconfig.tsbuildinfo +1 -0
  104. package/dist/types/common.type.d.ts +26 -0
  105. package/dist/types/common.type.js +26 -0
  106. package/dist/types/common.type.js.map +1 -0
  107. package/dist/types/mortgage-fsm.types.d.ts +180 -0
  108. package/dist/types/mortgage-fsm.types.js +130 -0
  109. package/dist/types/mortgage-fsm.types.js.map +1 -0
  110. package/dist/types/policy.types.d.ts +18 -0
  111. package/dist/types/policy.types.js +3 -0
  112. package/dist/types/policy.types.js.map +1 -0
  113. package/dist/types/property.type.d.ts +9 -0
  114. package/dist/types/property.type.js +15 -0
  115. package/dist/types/property.type.js.map +1 -0
  116. package/dist/types/social.enums.d.ts +15 -0
  117. package/dist/types/social.enums.js +22 -0
  118. package/dist/types/social.enums.js.map +1 -0
  119. package/dist/types/tenant.enums.d.ts +13 -0
  120. package/dist/types/tenant.enums.js +19 -0
  121. package/dist/types/tenant.enums.js.map +1 -0
  122. package/dist/types/transaction.type.d.ts +8 -0
  123. package/dist/types/transaction.type.js +14 -0
  124. package/dist/types/transaction.type.js.map +1 -0
  125. package/dist/types/user.enums.d.ts +10 -0
  126. package/dist/types/user.enums.js +16 -0
  127. package/dist/types/user.enums.js.map +1 -0
  128. package/entities/BaseEntity.ts +34 -0
  129. package/entities/TenantAwareEntity.ts +34 -0
  130. package/entities/TenantAwareRepository.ts +100 -0
  131. package/entities/amenity.entity.ts +10 -0
  132. package/entities/common.entity.ts +46 -0
  133. package/entities/common.pure.entity.ts +36 -0
  134. package/entities/index.ts +27 -0
  135. package/entities/mortgage-document.entity.ts +37 -0
  136. package/entities/mortgage-downpayment-installment.entity.ts +40 -0
  137. package/entities/mortgage-downpayment-payment.entity.ts +61 -0
  138. package/entities/mortgage-downpayment.entity.ts +43 -0
  139. package/entities/mortgage-step.entity.ts +33 -0
  140. package/entities/mortgage-type.entity.ts +31 -0
  141. package/entities/mortgage.entity.ts +89 -0
  142. package/entities/password_reset_tokens.entity.ts +25 -0
  143. package/entities/permission.entity.ts +12 -0
  144. package/entities/property-document.entity.ts +21 -0
  145. package/entities/property-media.entity.ts +23 -0
  146. package/entities/property.entity.ts +147 -0
  147. package/entities/refresh_token.entity.ts +16 -0
  148. package/entities/role.entity.ts +20 -0
  149. package/entities/settings.entity.ts +56 -0
  150. package/entities/social.entity.ts +27 -0
  151. package/entities/tenant.entity.ts +65 -0
  152. package/entities/transaction.entity.ts +56 -0
  153. package/entities/user.entity.ts +89 -0
  154. package/entities/user_suspensions.entity.ts +24 -0
  155. package/entities/wallet.entity.ts +54 -0
  156. package/guard/permission.guard.ts +42 -0
  157. package/guard/swagger-auth.guard.ts +9 -0
  158. package/helpers/ArrayHelper.ts +1 -0
  159. package/helpers/ConstantHelper.ts +101 -0
  160. package/helpers/CustomNamingStrategy.ts +27 -0
  161. package/helpers/DateHelper.ts +21 -0
  162. package/helpers/EmailHelper.ts +38 -0
  163. package/helpers/FileSystemHelper.ts +101 -0
  164. package/index.ts +9 -0
  165. package/middleware/TenantMiddleware.ts +52 -0
  166. package/package.json +46 -0
  167. package/pagination/index.ts +2 -0
  168. package/pagination/pagination.helper.ts +57 -0
  169. package/pagination/pagination.types.ts +21 -0
  170. package/standard-response.ts +16 -0
  171. package/tsconfig.json +33 -0
  172. package/types/common.type.ts +32 -0
  173. package/types/mortgage-fsm.types.ts +279 -0
  174. package/types/policy.types.ts +21 -0
  175. package/types/property.type.ts +10 -0
  176. package/types/social.enums.ts +17 -0
  177. package/types/tenant.enums.ts +14 -0
  178. package/types/transaction.type.ts +9 -0
  179. 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;">&copy; 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,9 @@
1
+ // Pagination
2
+ export * from './pagination';
3
+
4
+ // Types
5
+ export * from './standard-response';
6
+
7
+ // Entities
8
+ export * from './entities';
9
+
@@ -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,2 @@
1
+ export * from './pagination.types';
2
+ export * from './pagination.helper';
@@ -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
+ }