@forgedevstack/harbor 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 (130) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +927 -0
  3. package/dist/changelog/index.d.ts +3 -0
  4. package/dist/changelog/index.d.ts.map +1 -0
  5. package/dist/changelog/manager.d.ts +29 -0
  6. package/dist/changelog/manager.d.ts.map +1 -0
  7. package/dist/changelog/types.d.ts +41 -0
  8. package/dist/changelog/types.d.ts.map +1 -0
  9. package/dist/cli/index.d.ts +3 -0
  10. package/dist/cli/index.d.ts.map +1 -0
  11. package/dist/cli/index.js +43 -0
  12. package/dist/cli/index.js.map +1 -0
  13. package/dist/constants/config.const.d.ts +11 -0
  14. package/dist/constants/config.const.d.ts.map +1 -0
  15. package/dist/constants/defaults.const.d.ts +10 -0
  16. package/dist/constants/defaults.const.d.ts.map +1 -0
  17. package/dist/constants/http.const.d.ts +43 -0
  18. package/dist/constants/http.const.d.ts.map +1 -0
  19. package/dist/constants/index.d.ts +5 -0
  20. package/dist/constants/index.d.ts.map +1 -0
  21. package/dist/constants/validation.const.d.ts +35 -0
  22. package/dist/constants/validation.const.d.ts.map +1 -0
  23. package/dist/core/config.d.ts +6 -0
  24. package/dist/core/config.d.ts.map +1 -0
  25. package/dist/core/errorHandler.d.ts +25 -0
  26. package/dist/core/errorHandler.d.ts.map +1 -0
  27. package/dist/core/index.d.ts +6 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/router.d.ts +68 -0
  30. package/dist/core/router.d.ts.map +1 -0
  31. package/dist/core/server.d.ts +4 -0
  32. package/dist/core/server.d.ts.map +1 -0
  33. package/dist/database/connection.d.ts +39 -0
  34. package/dist/database/connection.d.ts.map +1 -0
  35. package/dist/database/index.d.ts +28 -0
  36. package/dist/database/index.d.ts.map +1 -0
  37. package/dist/database/model.d.ts +118 -0
  38. package/dist/database/model.d.ts.map +1 -0
  39. package/dist/database/schema.d.ts +63 -0
  40. package/dist/database/schema.d.ts.map +1 -0
  41. package/dist/database/types.d.ts +270 -0
  42. package/dist/database/types.d.ts.map +1 -0
  43. package/dist/docker/index.d.ts +3 -0
  44. package/dist/docker/index.d.ts.map +1 -0
  45. package/dist/docker/index.js +2 -0
  46. package/dist/docker/index.js.map +1 -0
  47. package/dist/docker/manager.d.ts +21 -0
  48. package/dist/docker/manager.d.ts.map +1 -0
  49. package/dist/i18n/index.d.ts +38 -0
  50. package/dist/i18n/index.d.ts.map +1 -0
  51. package/dist/i18n/locales/en.d.ts +2 -0
  52. package/dist/i18n/locales/en.d.ts.map +1 -0
  53. package/dist/i18n/locales/he.d.ts +2 -0
  54. package/dist/i18n/locales/he.d.ts.map +1 -0
  55. package/dist/index.cjs.js +24 -0
  56. package/dist/index.cjs.js.map +1 -0
  57. package/dist/index.d.ts +21 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.es.js +2094 -0
  60. package/dist/index.es.js.map +1 -0
  61. package/dist/logger-D7aJSi62.mjs +102 -0
  62. package/dist/logger-D7aJSi62.mjs.map +1 -0
  63. package/dist/logger-DEnWXtpk.js +3 -0
  64. package/dist/logger-DEnWXtpk.js.map +1 -0
  65. package/dist/manager-B1UKMjXW.js +4 -0
  66. package/dist/manager-B1UKMjXW.js.map +1 -0
  67. package/dist/manager-B6vqJgEn.mjs +152 -0
  68. package/dist/manager-B6vqJgEn.mjs.map +1 -0
  69. package/dist/portal.d.ts +13 -0
  70. package/dist/portal.d.ts.map +1 -0
  71. package/dist/types/config.types.d.ts +83 -0
  72. package/dist/types/config.types.d.ts.map +1 -0
  73. package/dist/types/docker.types.d.ts +92 -0
  74. package/dist/types/docker.types.d.ts.map +1 -0
  75. package/dist/types/index.d.ts +7 -0
  76. package/dist/types/index.d.ts.map +1 -0
  77. package/dist/types/logger.types.d.ts +35 -0
  78. package/dist/types/logger.types.d.ts.map +1 -0
  79. package/dist/types/route.types.d.ts +78 -0
  80. package/dist/types/route.types.d.ts.map +1 -0
  81. package/dist/types/server.types.d.ts +67 -0
  82. package/dist/types/server.types.d.ts.map +1 -0
  83. package/dist/types/validation.types.d.ts +64 -0
  84. package/dist/types/validation.types.d.ts.map +1 -0
  85. package/dist/utils/helpers.d.ts +7 -0
  86. package/dist/utils/helpers.d.ts.map +1 -0
  87. package/dist/utils/httpLogger.d.ts +52 -0
  88. package/dist/utils/httpLogger.d.ts.map +1 -0
  89. package/dist/utils/index.d.ts +6 -0
  90. package/dist/utils/index.d.ts.map +1 -0
  91. package/dist/utils/logger.d.ts +6 -0
  92. package/dist/utils/logger.d.ts.map +1 -0
  93. package/dist/utils/object.d.ts +6 -0
  94. package/dist/utils/object.d.ts.map +1 -0
  95. package/dist/validation/index.d.ts +5 -0
  96. package/dist/validation/index.d.ts.map +1 -0
  97. package/dist/validation/index.js +2 -0
  98. package/dist/validation/index.js.map +1 -0
  99. package/dist/validation/mongo.d.ts +13 -0
  100. package/dist/validation/mongo.d.ts.map +1 -0
  101. package/dist/validation/paramValidators.d.ts +18 -0
  102. package/dist/validation/paramValidators.d.ts.map +1 -0
  103. package/dist/validation/validators.d.ts +9 -0
  104. package/dist/validation/validators.d.ts.map +1 -0
  105. package/harbor.config.example.json +72 -0
  106. package/package.json +107 -0
  107. package/templates/default/.eslintrc.json +45 -0
  108. package/templates/default/README.md +97 -0
  109. package/templates/default/constants/config.ts +26 -0
  110. package/templates/default/constants/http.ts +32 -0
  111. package/templates/default/constants/index.ts +3 -0
  112. package/templates/default/controllers/index.ts +6 -0
  113. package/templates/default/controllers/user.controller.ts +77 -0
  114. package/templates/default/env.example +22 -0
  115. package/templates/default/harbor.version.json +7 -0
  116. package/templates/default/models/index.ts +6 -0
  117. package/templates/default/models/user.model.ts +68 -0
  118. package/templates/default/package.json +44 -0
  119. package/templates/default/routes/index.ts +12 -0
  120. package/templates/default/routes/user.routes.ts +21 -0
  121. package/templates/default/server.ts +45 -0
  122. package/templates/default/services/index.ts +6 -0
  123. package/templates/default/services/user.service.ts +84 -0
  124. package/templates/default/tsconfig.json +35 -0
  125. package/templates/default/types/index.ts +57 -0
  126. package/templates/default/utils/asyncHandler.ts +14 -0
  127. package/templates/default/utils/index.ts +5 -0
  128. package/templates/default/utils/logger.ts +52 -0
  129. package/templates/default/utils/response.ts +24 -0
  130. package/templates/default/utils/validation.ts +23 -0
@@ -0,0 +1,97 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ {{PROJECT_DESCRIPTION}}
4
+
5
+ Built with [Harbor](https://github.com/yaghobieh/Harbor) - The pipeline for Node.js backends.
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Express Server** - Fast and minimal web framework
10
+ - 📦 **MongoDB** - Database with Harbor ODM
11
+ - 🔒 **TypeScript** - Type-safe development
12
+ - 📝 **ESLint** - Code linting and formatting
13
+ - 🧪 **Vitest** - Testing framework
14
+ - 📁 **Clean Architecture** - Organized folder structure
15
+
16
+ ## Getting Started
17
+
18
+ ### Prerequisites
19
+
20
+ - Node.js 18+
21
+ - MongoDB (local or Atlas)
22
+
23
+ ### Installation
24
+
25
+ ```bash
26
+ # Install dependencies
27
+ npm install
28
+
29
+ # Copy environment variables
30
+ cp .env.example .env
31
+
32
+ # Start development server
33
+ npm run dev
34
+ ```
35
+
36
+ ### Available Scripts
37
+
38
+ | Script | Description |
39
+ |--------|-------------|
40
+ | `npm run dev` | Start development server with hot reload |
41
+ | `npm run build` | Build for production |
42
+ | `npm start` | Start production server |
43
+ | `npm run lint` | Run ESLint |
44
+ | `npm run lint:fix` | Fix ESLint errors |
45
+ | `npm test` | Run tests |
46
+
47
+ ## Project Structure
48
+
49
+ ```
50
+ ├── server.ts # Application entry point
51
+ ├── routes/ # Route definitions
52
+ │ ├── index.ts # Route aggregator
53
+ │ └── user.routes.ts # User routes
54
+ ├── controllers/ # Request handlers
55
+ │ └── user.controller.ts
56
+ ├── services/ # Business logic
57
+ │ └── user.service.ts
58
+ ├── models/ # Database models
59
+ │ └── user.model.ts
60
+ ├── types/ # TypeScript definitions
61
+ ├── utils/ # Utility functions
62
+ ├── constants/ # App constants & config
63
+ └── package.json
64
+ ```
65
+
66
+ ## API Endpoints
67
+
68
+ ### Users
69
+
70
+ | Method | Endpoint | Description |
71
+ |--------|----------|-------------|
72
+ | GET | `/api/users` | Get all users |
73
+ | GET | `/api/users/:id` | Get user by ID |
74
+ | POST | `/api/users` | Create new user |
75
+ | PUT | `/api/users/:id` | Update user |
76
+ | DELETE | `/api/users/:id` | Delete user |
77
+
78
+ ### Health Check
79
+
80
+ | Method | Endpoint | Description |
81
+ |--------|----------|-------------|
82
+ | GET | `/health` | Server health status |
83
+
84
+ ## Environment Variables
85
+
86
+ | Variable | Description | Default |
87
+ |----------|-------------|---------|
88
+ | `PORT` | Server port | `3000` |
89
+ | `NODE_ENV` | Environment | `development` |
90
+ | `MONGODB_URI` | MongoDB connection string | - |
91
+ | `DB_NAME` | Database name | `harbor_app` |
92
+ | `JWT_SECRET` | JWT signing secret | - |
93
+
94
+ ## License
95
+
96
+ MIT
97
+
@@ -0,0 +1,26 @@
1
+ import 'dotenv/config';
2
+
3
+ export const config = {
4
+ // Server
5
+ PORT: parseInt(process.env.PORT || '3000', 10),
6
+ NODE_ENV: process.env.NODE_ENV || 'development',
7
+
8
+ // Database
9
+ MONGODB_URI: process.env.MONGODB_URI || '',
10
+ DB_NAME: process.env.DB_NAME || 'harbor_app',
11
+
12
+ // JWT
13
+ JWT_SECRET: process.env.JWT_SECRET || 'your-secret-key',
14
+ JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '7d',
15
+
16
+ // CORS
17
+ CORS_ORIGIN: process.env.CORS_ORIGIN || '*',
18
+
19
+ // Logging
20
+ LOG_LEVEL: process.env.LOG_LEVEL || 'info',
21
+
22
+ // Rate Limiting
23
+ RATE_LIMIT_WINDOW: parseInt(process.env.RATE_LIMIT_WINDOW || '900000', 10), // 15 minutes
24
+ RATE_LIMIT_MAX: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
25
+ } as const;
26
+
@@ -0,0 +1,32 @@
1
+ export const HTTP_STATUS = {
2
+ OK: 200,
3
+ CREATED: 201,
4
+ NO_CONTENT: 204,
5
+ BAD_REQUEST: 400,
6
+ UNAUTHORIZED: 401,
7
+ FORBIDDEN: 403,
8
+ NOT_FOUND: 404,
9
+ CONFLICT: 409,
10
+ UNPROCESSABLE_ENTITY: 422,
11
+ TOO_MANY_REQUESTS: 429,
12
+ INTERNAL_SERVER_ERROR: 500,
13
+ BAD_GATEWAY: 502,
14
+ SERVICE_UNAVAILABLE: 503,
15
+ } as const;
16
+
17
+ export const HTTP_MESSAGES = {
18
+ [HTTP_STATUS.OK]: 'Success',
19
+ [HTTP_STATUS.CREATED]: 'Created successfully',
20
+ [HTTP_STATUS.NO_CONTENT]: 'Deleted successfully',
21
+ [HTTP_STATUS.BAD_REQUEST]: 'Bad request',
22
+ [HTTP_STATUS.UNAUTHORIZED]: 'Unauthorized',
23
+ [HTTP_STATUS.FORBIDDEN]: 'Access denied',
24
+ [HTTP_STATUS.NOT_FOUND]: 'Resource not found',
25
+ [HTTP_STATUS.CONFLICT]: 'Resource already exists',
26
+ [HTTP_STATUS.UNPROCESSABLE_ENTITY]: 'Validation error',
27
+ [HTTP_STATUS.TOO_MANY_REQUESTS]: 'Too many requests',
28
+ [HTTP_STATUS.INTERNAL_SERVER_ERROR]: 'Internal server error',
29
+ [HTTP_STATUS.BAD_GATEWAY]: 'Bad gateway',
30
+ [HTTP_STATUS.SERVICE_UNAVAILABLE]: 'Service unavailable',
31
+ } as const;
32
+
@@ -0,0 +1,3 @@
1
+ export { config } from './config';
2
+ export { HTTP_STATUS, HTTP_MESSAGES } from './http';
3
+
@@ -0,0 +1,6 @@
1
+ export { UserController } from './user.controller';
2
+
3
+ // Add more controller exports here:
4
+ // export { ProductController } from './product.controller';
5
+ // export { OrderController } from './order.controller';
6
+
@@ -0,0 +1,77 @@
1
+ import { Request, Response } from 'express';
2
+ import { UserService } from '../services';
3
+ import { CreateUserDto, UpdateUserDto } from '../types';
4
+
5
+ export class UserController {
6
+ /**
7
+ * Get all users
8
+ */
9
+ static async getAll(req: Request, res: Response) {
10
+ const { page = 1, limit = 10 } = req.query;
11
+
12
+ const result = await UserService.findAll({
13
+ page: Number(page),
14
+ limit: Number(limit),
15
+ });
16
+
17
+ return res.json(result);
18
+ }
19
+
20
+ /**
21
+ * Get user by ID
22
+ */
23
+ static async getById(req: Request, res: Response) {
24
+ const { id } = req.params;
25
+
26
+ const user = await UserService.findById(id);
27
+
28
+ if (!user) {
29
+ return res.status(404).json({ error: 'User not found' });
30
+ }
31
+
32
+ return res.json(user);
33
+ }
34
+
35
+ /**
36
+ * Create new user
37
+ */
38
+ static async create(req: Request, res: Response) {
39
+ const data: CreateUserDto = req.body;
40
+
41
+ const user = await UserService.create(data);
42
+
43
+ return res.status(201).json(user);
44
+ }
45
+
46
+ /**
47
+ * Update user
48
+ */
49
+ static async update(req: Request, res: Response) {
50
+ const { id } = req.params;
51
+ const data: UpdateUserDto = req.body;
52
+
53
+ const user = await UserService.update(id, data);
54
+
55
+ if (!user) {
56
+ return res.status(404).json({ error: 'User not found' });
57
+ }
58
+
59
+ return res.json(user);
60
+ }
61
+
62
+ /**
63
+ * Delete user
64
+ */
65
+ static async delete(req: Request, res: Response) {
66
+ const { id } = req.params;
67
+
68
+ const deleted = await UserService.delete(id);
69
+
70
+ if (!deleted) {
71
+ return res.status(404).json({ error: 'User not found' });
72
+ }
73
+
74
+ return res.status(204).send();
75
+ }
76
+ }
77
+
@@ -0,0 +1,22 @@
1
+ # Server Configuration
2
+ PORT=3000
3
+ NODE_ENV=development
4
+
5
+ # Database
6
+ MONGODB_URI=mongodb://localhost:27017
7
+ DB_NAME=harbor_app
8
+
9
+ # JWT Authentication
10
+ JWT_SECRET=your-super-secret-key-change-in-production
11
+ JWT_EXPIRES_IN=7d
12
+
13
+ # CORS
14
+ CORS_ORIGIN=http://localhost:3000
15
+
16
+ # Logging
17
+ LOG_LEVEL=info
18
+
19
+ # Rate Limiting
20
+ RATE_LIMIT_WINDOW=900000
21
+ RATE_LIMIT_MAX=100
22
+
@@ -0,0 +1,7 @@
1
+ {
2
+ "harborVersion": "1.3.0",
3
+ "templateVersion": "1.0.0",
4
+ "createdAt": "{{CREATED_AT}}",
5
+ "templateName": "default"
6
+ }
7
+
@@ -0,0 +1,6 @@
1
+ export { User } from './user.model';
2
+
3
+ // Add more model exports here:
4
+ // export { Product } from './product.model';
5
+ // export { Order } from './order.model';
6
+
@@ -0,0 +1,68 @@
1
+ import { Schema, model } from 'harbor/database';
2
+
3
+ // Define the User schema
4
+ const userSchema = new Schema({
5
+ email: {
6
+ type: 'string',
7
+ required: true,
8
+ unique: true,
9
+ },
10
+ name: {
11
+ type: 'string',
12
+ required: true,
13
+ },
14
+ password: {
15
+ type: 'string',
16
+ required: true,
17
+ select: false, // Don't include in query results by default
18
+ },
19
+ role: {
20
+ type: 'string',
21
+ enum: ['user', 'admin', 'moderator'],
22
+ default: 'user',
23
+ },
24
+ isActive: {
25
+ type: 'boolean',
26
+ default: true,
27
+ },
28
+ profile: {
29
+ avatar: { type: 'string' },
30
+ bio: { type: 'string' },
31
+ },
32
+ lastLogin: {
33
+ type: 'date',
34
+ },
35
+ }, {
36
+ timestamps: true, // Adds createdAt and updatedAt
37
+ collection: 'users',
38
+ });
39
+
40
+ // Virtual for full display name
41
+ userSchema.virtual('displayName').get(function() {
42
+ return this.name || this.email.split('@')[0];
43
+ });
44
+
45
+ // Pre-save hook for password hashing
46
+ userSchema.pre('save', async function(next) {
47
+ if (this.isModified('password')) {
48
+ // Hash password here (use bcrypt in production)
49
+ // this.password = await bcrypt.hash(this.password, 10);
50
+ }
51
+ next();
52
+ });
53
+
54
+ // Static method for finding by email
55
+ userSchema.statics.findByEmail = function(email: string) {
56
+ return this.findOne({ email: email.toLowerCase() });
57
+ };
58
+
59
+ // Instance method for comparing passwords
60
+ userSchema.methods.comparePassword = async function(candidatePassword: string) {
61
+ // Compare passwords here (use bcrypt in production)
62
+ // return bcrypt.compare(candidatePassword, this.password);
63
+ return candidatePassword === this.password;
64
+ };
65
+
66
+ // Export the model
67
+ export const User = model('User', userSchema);
68
+
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "description": "{{PROJECT_DESCRIPTION}}",
5
+ "main": "dist/server.js",
6
+ "scripts": {
7
+ "dev": "tsx watch server.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/server.js",
10
+ "lint": "eslint . --ext .ts",
11
+ "lint:fix": "eslint . --ext .ts --fix",
12
+ "test": "vitest",
13
+ "test:coverage": "vitest --coverage"
14
+ },
15
+ "keywords": [
16
+ "nodejs",
17
+ "express",
18
+ "mongodb",
19
+ "harbor",
20
+ "api"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "dotenv": "^16.3.1",
26
+ "express": "^4.18.2",
27
+ "harbor": "^1.2.0",
28
+ "mongodb": "^6.3.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/express": "^4.17.21",
32
+ "@types/node": "^20.10.0",
33
+ "@typescript-eslint/eslint-plugin": "^6.13.0",
34
+ "@typescript-eslint/parser": "^6.13.0",
35
+ "eslint": "^8.55.0",
36
+ "tsx": "^4.6.0",
37
+ "typescript": "^5.3.0",
38
+ "vitest": "^1.0.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ }
43
+ }
44
+
@@ -0,0 +1,12 @@
1
+ import { Router } from 'express';
2
+ import { userRoutes } from './user.routes';
3
+
4
+ export const routes = Router();
5
+
6
+ // Register all route modules
7
+ routes.use('/users', userRoutes);
8
+
9
+ // Add more routes here:
10
+ // routes.use('/products', productRoutes);
11
+ // routes.use('/orders', orderRoutes);
12
+
@@ -0,0 +1,21 @@
1
+ import { Router } from 'express';
2
+ import { GET, POST, PUT, DELETE } from 'harbor';
3
+ import { UserController } from '../controllers';
4
+
5
+ export const userRoutes = Router();
6
+
7
+ // GET /api/users - Get all users
8
+ userRoutes.get('/', GET(UserController.getAll));
9
+
10
+ // GET /api/users/:id - Get user by ID
11
+ userRoutes.get('/:id', GET(UserController.getById));
12
+
13
+ // POST /api/users - Create new user
14
+ userRoutes.post('/', POST(UserController.create));
15
+
16
+ // PUT /api/users/:id - Update user
17
+ userRoutes.put('/:id', PUT(UserController.update));
18
+
19
+ // DELETE /api/users/:id - Delete user
20
+ userRoutes.delete('/:id', DELETE(UserController.delete));
21
+
@@ -0,0 +1,45 @@
1
+ import { createServer, connect, httpLogger } from 'harbor';
2
+ import { routes } from './routes';
3
+ import { config } from './constants';
4
+
5
+ async function bootstrap() {
6
+ // Create server with Harbor
7
+ const app = createServer({
8
+ port: config.PORT,
9
+ cors: true,
10
+ helmet: true,
11
+ json: true,
12
+ urlencoded: true,
13
+ });
14
+
15
+ // HTTP request logging
16
+ app.use(httpLogger({
17
+ format: 'dev',
18
+ skip: (req) => req.path === '/health',
19
+ }));
20
+
21
+ // Connect to MongoDB (optional)
22
+ if (config.MONGODB_URI) {
23
+ await connect(config.MONGODB_URI, {
24
+ dbName: config.DB_NAME,
25
+ });
26
+ console.log('📦 Connected to MongoDB');
27
+ }
28
+
29
+ // Register all routes
30
+ app.use('/api', routes);
31
+
32
+ // Health check endpoint
33
+ app.get('/health', (req, res) => {
34
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
35
+ });
36
+
37
+ // Start server
38
+ app.listen(config.PORT, () => {
39
+ console.log(`🚀 Server running on http://localhost:${config.PORT}`);
40
+ console.log(`📚 API Docs: http://localhost:${config.PORT}/api-docs`);
41
+ });
42
+ }
43
+
44
+ bootstrap().catch(console.error);
45
+
@@ -0,0 +1,6 @@
1
+ export { UserService } from './user.service';
2
+
3
+ // Add more service exports here:
4
+ // export { ProductService } from './product.service';
5
+ // export { OrderService } from './order.service';
6
+
@@ -0,0 +1,84 @@
1
+ import { User } from '../models';
2
+ import { CreateUserDto, UpdateUserDto, PaginationOptions, PaginatedResult } from '../types';
3
+
4
+ export class UserService {
5
+ /**
6
+ * Find all users with pagination
7
+ */
8
+ static async findAll(options: PaginationOptions): Promise<PaginatedResult<typeof User>> {
9
+ const { page = 1, limit = 10 } = options;
10
+ const skip = (page - 1) * limit;
11
+
12
+ const [users, total] = await Promise.all([
13
+ User.find()
14
+ .skip(skip)
15
+ .limit(limit)
16
+ .sort({ createdAt: -1 }),
17
+ User.countDocuments(),
18
+ ]);
19
+
20
+ return {
21
+ data: users,
22
+ pagination: {
23
+ page,
24
+ limit,
25
+ total,
26
+ totalPages: Math.ceil(total / limit),
27
+ },
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Find user by ID
33
+ */
34
+ static async findById(id: string) {
35
+ return User.findById(id);
36
+ }
37
+
38
+ /**
39
+ * Find user by email
40
+ */
41
+ static async findByEmail(email: string) {
42
+ return User.findOne({ email: email.toLowerCase() });
43
+ }
44
+
45
+ /**
46
+ * Create new user
47
+ */
48
+ static async create(data: CreateUserDto) {
49
+ const user = await User.create({
50
+ ...data,
51
+ email: data.email.toLowerCase(),
52
+ });
53
+
54
+ return user;
55
+ }
56
+
57
+ /**
58
+ * Update user
59
+ */
60
+ static async update(id: string, data: UpdateUserDto) {
61
+ return User.findByIdAndUpdate(
62
+ id,
63
+ { $set: data },
64
+ { new: true }
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Delete user
70
+ */
71
+ static async delete(id: string) {
72
+ const result = await User.deleteOne({ _id: id });
73
+ return result.deletedCount > 0;
74
+ }
75
+
76
+ /**
77
+ * Check if email exists
78
+ */
79
+ static async emailExists(email: string): Promise<boolean> {
80
+ const count = await User.countDocuments({ email: email.toLowerCase() });
81
+ return count > 0;
82
+ }
83
+ }
84
+
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": ".",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "noImplicitAny": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "baseUrl": ".",
23
+ "paths": {
24
+ "@/*": ["./*"]
25
+ }
26
+ },
27
+ "include": [
28
+ "**/*.ts"
29
+ ],
30
+ "exclude": [
31
+ "node_modules",
32
+ "dist"
33
+ ]
34
+ }
35
+
@@ -0,0 +1,57 @@
1
+ // User DTOs
2
+ export interface CreateUserDto {
3
+ email: string;
4
+ name: string;
5
+ password: string;
6
+ role?: 'user' | 'admin' | 'moderator';
7
+ }
8
+
9
+ export interface UpdateUserDto {
10
+ email?: string;
11
+ name?: string;
12
+ password?: string;
13
+ role?: 'user' | 'admin' | 'moderator';
14
+ isActive?: boolean;
15
+ profile?: {
16
+ avatar?: string;
17
+ bio?: string;
18
+ };
19
+ }
20
+
21
+ // Pagination
22
+ export interface PaginationOptions {
23
+ page?: number;
24
+ limit?: number;
25
+ sort?: string;
26
+ order?: 'asc' | 'desc';
27
+ }
28
+
29
+ export interface Pagination {
30
+ page: number;
31
+ limit: number;
32
+ total: number;
33
+ totalPages: number;
34
+ }
35
+
36
+ export interface PaginatedResult<T> {
37
+ data: T[];
38
+ pagination: Pagination;
39
+ }
40
+
41
+ // API Response
42
+ export interface ApiResponse<T = unknown> {
43
+ success: boolean;
44
+ data?: T;
45
+ error?: string;
46
+ message?: string;
47
+ }
48
+
49
+ // Request with user (for auth)
50
+ export interface AuthenticatedRequest extends Express.Request {
51
+ user?: {
52
+ id: string;
53
+ email: string;
54
+ role: string;
55
+ };
56
+ }
57
+
@@ -0,0 +1,14 @@
1
+ import { Request, Response, NextFunction, RequestHandler } from 'express';
2
+
3
+ /**
4
+ * Wraps async route handlers to automatically catch errors
5
+ * and pass them to Express error handling middleware
6
+ */
7
+ export const asyncHandler = (
8
+ fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>
9
+ ): RequestHandler => {
10
+ return (req: Request, res: Response, next: NextFunction) => {
11
+ Promise.resolve(fn(req, res, next)).catch(next);
12
+ };
13
+ };
14
+
@@ -0,0 +1,5 @@
1
+ export { logger } from './logger';
2
+ export { asyncHandler } from './asyncHandler';
3
+ export { formatResponse, formatError } from './response';
4
+ export { validateObjectId } from './validation';
5
+