create-listablelabs-api 1.0.1

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 (32) hide show
  1. package/README.md +142 -0
  2. package/bin/cli.js +37 -0
  3. package/bin/commands/add.js +460 -0
  4. package/bin/commands/create.js +481 -0
  5. package/package.json +39 -0
  6. package/templates/base/.dockerignore +19 -0
  7. package/templates/base/.env.example +18 -0
  8. package/templates/base/.eslintrc.js +31 -0
  9. package/templates/base/Dockerfile +48 -0
  10. package/templates/base/README.md +295 -0
  11. package/templates/base/docker-compose.yml +55 -0
  12. package/templates/base/jest.config.js +24 -0
  13. package/templates/base/package.json +41 -0
  14. package/templates/base/src/app.js +103 -0
  15. package/templates/base/src/config/index.js +36 -0
  16. package/templates/base/src/controllers/exampleController.js +148 -0
  17. package/templates/base/src/database/baseModel.js +160 -0
  18. package/templates/base/src/database/index.js +108 -0
  19. package/templates/base/src/middlewares/errorHandler.js +155 -0
  20. package/templates/base/src/middlewares/index.js +49 -0
  21. package/templates/base/src/middlewares/rateLimiter.js +85 -0
  22. package/templates/base/src/middlewares/requestLogger.js +50 -0
  23. package/templates/base/src/middlewares/validator.js +107 -0
  24. package/templates/base/src/models/example.js +117 -0
  25. package/templates/base/src/models/index.js +6 -0
  26. package/templates/base/src/routes/v1/exampleRoutes.js +89 -0
  27. package/templates/base/src/routes/v1/index.js +19 -0
  28. package/templates/base/src/server.js +80 -0
  29. package/templates/base/src/utils/logger.js +61 -0
  30. package/templates/base/src/utils/response.js +117 -0
  31. package/templates/base/tests/app.test.js +215 -0
  32. package/templates/base/tests/setup.js +33 -0
@@ -0,0 +1,295 @@
1
+ # ListableLabs Microservice Template
2
+
3
+ A production-ready Node.js/Express microservice template with MongoDB Atlas, Zod validation, Pino logging, and best practices.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ```bash
8
+ # Clone and setup
9
+ git clone <repo-url> my-service
10
+ cd my-service
11
+ npm install
12
+
13
+ # Configure environment
14
+ cp .env.example .env
15
+ # Edit .env with your MongoDB Atlas URI and service name
16
+
17
+ # Start development
18
+ npm run dev
19
+ ```
20
+
21
+ ## 📁 Project Structure
22
+
23
+ ```
24
+ ├── src/
25
+ │ ├── config/ # Configuration (env vars)
26
+ │ ├── controllers/ # Request handlers
27
+ │ ├── database/ # MongoDB connection & base model
28
+ │ ├── middlewares/ # Express middlewares
29
+ │ │ ├── errorHandler.js # Centralized error handling
30
+ │ │ ├── rateLimiter.js # Rate limiting
31
+ │ │ ├── requestLogger.js # Pino HTTP logging
32
+ │ │ └── validator.js # Zod validation
33
+ │ ├── models/ # Mongoose models + Zod schemas
34
+ │ ├── routes/v1/ # API routes
35
+ │ ├── utils/
36
+ │ │ ├── logger.js # Pino logger
37
+ │ │ └── response.js # Standard responses
38
+ │ ├── app.js # Express setup
39
+ │ └── server.js # Entry point
40
+ ├── tests/ # Jest tests with mongodb-memory-server
41
+ ├── Dockerfile
42
+ └── docker-compose.yml
43
+ ```
44
+
45
+ ## 🔧 Scripts
46
+
47
+ ```bash
48
+ npm start # Production server
49
+ npm run dev # Development with nodemon
50
+ npm test # Run tests with coverage
51
+ npm run lint # ESLint check
52
+ npm run lint:fix # Auto-fix lint errors
53
+ ```
54
+
55
+ ## 📝 Creating New Endpoints
56
+
57
+ ### 1. Create Model with Zod Schema
58
+
59
+ ```javascript
60
+ // src/models/user.js
61
+ const { createModel } = require('../database/baseModel');
62
+ const { z } = require('zod');
63
+
64
+ // Zod schemas for validation
65
+ const userZodSchema = {
66
+ create: z.object({
67
+ email: z.string().email(),
68
+ name: z.string().min(1).max(100),
69
+ role: z.enum(['user', 'admin']).default('user'),
70
+ }),
71
+ update: z.object({
72
+ name: z.string().min(1).max(100).optional(),
73
+ role: z.enum(['user', 'admin']).optional(),
74
+ }),
75
+ };
76
+
77
+ // Mongoose model
78
+ const User = createModel('User', {
79
+ email: { type: String, required: true, unique: true },
80
+ name: { type: String, required: true },
81
+ role: { type: String, enum: ['user', 'admin'], default: 'user' },
82
+ }, {
83
+ softDelete: true, // enables soft delete
84
+ indexes: [{ fields: { email: 1 }, options: { unique: true } }],
85
+ });
86
+
87
+ module.exports = { User, userZodSchema };
88
+ ```
89
+
90
+ ### 2. Create Controller
91
+
92
+ ```javascript
93
+ // src/controllers/userController.js
94
+ const { asyncHandler, NotFoundError } = require('../middlewares');
95
+ const { sendSuccess, sendCreated, sendPaginated } = require('../utils/response');
96
+ const { User } = require('../models');
97
+
98
+ const getAll = asyncHandler(async (req, res) => {
99
+ const result = await User.paginate({}, req.query);
100
+ sendPaginated(res, result.data, result.pagination);
101
+ });
102
+
103
+ const create = asyncHandler(async (req, res) => {
104
+ const user = await User.create(req.body);
105
+ sendCreated(res, user);
106
+ });
107
+
108
+ module.exports = { getAll, create };
109
+ ```
110
+
111
+ ### 3. Create Routes with Validation
112
+
113
+ ```javascript
114
+ // src/routes/v1/userRoutes.js
115
+ const { Router } = require('express');
116
+ const { validate, z } = require('../../middlewares');
117
+ const { userZodSchema } = require('../../models');
118
+ const userController = require('../../controllers/userController');
119
+
120
+ const router = Router();
121
+
122
+ router.get('/', userController.getAll);
123
+ router.post('/', validate({ body: userZodSchema.create }), userController.create);
124
+
125
+ module.exports = router;
126
+ ```
127
+
128
+ ### 4. Register in routes/v1/index.js
129
+
130
+ ```javascript
131
+ const userRoutes = require('./userRoutes');
132
+ router.use('/users', userRoutes);
133
+ ```
134
+
135
+ ## ✅ Validation with Zod
136
+
137
+ ```javascript
138
+ const { validate, z, schemas } = require('./middlewares');
139
+
140
+ // Custom schema
141
+ const schema = {
142
+ body: z.object({
143
+ email: z.string().email(),
144
+ age: z.number().int().min(18),
145
+ }),
146
+ query: z.object({
147
+ page: z.coerce.number().int().min(1).default(1),
148
+ }),
149
+ params: z.object({
150
+ id: schemas.mongoId, // reusable pattern
151
+ }),
152
+ };
153
+
154
+ router.post('/users/:id', validate(schema), controller.update);
155
+ ```
156
+
157
+ ### Reusable Schema Patterns
158
+
159
+ ```javascript
160
+ const { schemas } = require('./middlewares');
161
+
162
+ schemas.mongoId // MongoDB ObjectId validation
163
+ schemas.email // Email with lowercase trim
164
+ schemas.pagination // page, limit, sortBy, sortOrder
165
+ schemas.dateRange // startDate, endDate validation
166
+ ```
167
+
168
+ ## 🪵 Logging with Pino
169
+
170
+ ```javascript
171
+ const { logger, createChildLogger } = require('./utils/logger');
172
+
173
+ // Direct logging
174
+ logger.info({ userId: 123 }, 'User logged in');
175
+ logger.error({ err }, 'Database error');
176
+
177
+ // Child logger with context
178
+ const log = createChildLogger({ module: 'paymentService' });
179
+ log.info({ orderId: 456 }, 'Processing payment');
180
+ ```
181
+
182
+ **Log Output (JSON in production):**
183
+ ```json
184
+ {
185
+ "level": 30,
186
+ "time": "2024-01-15T10:30:00.000Z",
187
+ "service": "my-service",
188
+ "module": "paymentService",
189
+ "orderId": 456,
190
+ "msg": "Processing payment"
191
+ }
192
+ ```
193
+
194
+ ## ❌ Error Handling
195
+
196
+ ```javascript
197
+ const {
198
+ BadRequestError, // 400
199
+ UnauthorizedError, // 401
200
+ ForbiddenError, // 403
201
+ NotFoundError, // 404
202
+ ConflictError, // 409
203
+ ValidationError, // 422
204
+ TooManyRequestsError, // 429
205
+ } = require('./middlewares');
206
+
207
+ // Throw anywhere - caught by error handler
208
+ throw new NotFoundError('User not found');
209
+ throw new ValidationError('Invalid input', [
210
+ { field: 'email', message: 'Invalid format' }
211
+ ]);
212
+ ```
213
+
214
+ **Error Response:**
215
+ ```json
216
+ {
217
+ "success": false,
218
+ "error": {
219
+ "code": "NOT_FOUND",
220
+ "message": "User not found"
221
+ },
222
+ "requestId": "abc-123",
223
+ "timestamp": "2024-01-15T10:30:00.000Z"
224
+ }
225
+ ```
226
+
227
+ ## 📊 Standard Responses
228
+
229
+ ```javascript
230
+ const { sendSuccess, sendCreated, sendPaginated, sendNoContent } = require('./utils/response');
231
+
232
+ sendSuccess(res, data); // 200
233
+ sendCreated(res, newUser); // 201
234
+ sendPaginated(res, items, pagination); // 200 with pagination
235
+ sendNoContent(res); // 204
236
+ ```
237
+
238
+ ## 🗄️ MongoDB Atlas Setup
239
+
240
+ 1. Create cluster at [MongoDB Atlas](https://www.mongodb.com/atlas)
241
+ 2. Get connection string: `mongodb+srv://<user>:<pass>@cluster.mongodb.net/<db>`
242
+ 3. Add to `.env`:
243
+ ```
244
+ MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/mydb?retryWrites=true&w=majority
245
+ ```
246
+ 4. Whitelist your IP in Atlas Network Access
247
+
248
+ ## 🧪 Testing
249
+
250
+ Tests use `mongodb-memory-server` for isolated in-memory MongoDB:
251
+
252
+ ```bash
253
+ npm test # Run all tests
254
+ npm run test:watch # Watch mode
255
+ ```
256
+
257
+ ```javascript
258
+ // tests/user.test.js
259
+ const request = require('supertest');
260
+ const app = require('../src/app');
261
+ const { User } = require('../src/models');
262
+
263
+ describe('POST /api/v1/users', () => {
264
+ it('should create user', async () => {
265
+ const res = await request(app)
266
+ .post('/api/v1/users')
267
+ .send({ email: 'test@example.com', name: 'Test' });
268
+
269
+ expect(res.status).toBe(201);
270
+ expect(res.body.data.email).toBe('test@example.com');
271
+ });
272
+ });
273
+ ```
274
+
275
+ ## 🐳 Docker
276
+
277
+ ```bash
278
+ docker build -t my-service .
279
+ docker run -p 3000:3000 -e MONGODB_URI=... my-service
280
+ ```
281
+
282
+ ## 🏥 Health Checks
283
+
284
+ - `GET /health` - Liveness (always 200 if running)
285
+ - `GET /ready` - Readiness (checks DB connection)
286
+
287
+ ## 📋 New Service Checklist
288
+
289
+ - [ ] Update `SERVICE_NAME` in `.env`
290
+ - [ ] Update `name` in `package.json`
291
+ - [ ] Configure `MONGODB_URI` for your database
292
+ - [ ] Remove example routes/models
293
+ - [ ] Add your models in `src/models/`
294
+ - [ ] Add your routes in `src/routes/v1/`
295
+ - [ ] Write tests
@@ -0,0 +1,55 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ api:
5
+ build:
6
+ context: .
7
+ target: production
8
+ ports:
9
+ - "3000:3000"
10
+ environment:
11
+ - NODE_ENV=production
12
+ - PORT=3000
13
+ - SERVICE_NAME=my-service
14
+ - LOG_LEVEL=info
15
+ healthcheck:
16
+ test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
17
+ interval: 30s
18
+ timeout: 3s
19
+ retries: 3
20
+ start_period: 5s
21
+ restart: unless-stopped
22
+
23
+ # Uncomment and configure as needed:
24
+
25
+ # redis:
26
+ # image: redis:7-alpine
27
+ # ports:
28
+ # - "6379:6379"
29
+ # volumes:
30
+ # - redis-data:/data
31
+ # healthcheck:
32
+ # test: ["CMD", "redis-cli", "ping"]
33
+ # interval: 10s
34
+ # timeout: 5s
35
+ # retries: 5
36
+
37
+ # postgres:
38
+ # image: postgres:15-alpine
39
+ # ports:
40
+ # - "5432:5432"
41
+ # environment:
42
+ # - POSTGRES_USER=user
43
+ # - POSTGRES_PASSWORD=password
44
+ # - POSTGRES_DB=mydb
45
+ # volumes:
46
+ # - postgres-data:/var/lib/postgresql/data
47
+ # healthcheck:
48
+ # test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
49
+ # interval: 10s
50
+ # timeout: 5s
51
+ # retries: 5
52
+
53
+ # volumes:
54
+ # redis-data:
55
+ # postgres-data:
@@ -0,0 +1,24 @@
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ coverageDirectory: 'coverage',
4
+ collectCoverageFrom: [
5
+ 'src/**/*.js',
6
+ '!src/server.js',
7
+ ],
8
+ coverageThreshold: {
9
+ global: {
10
+ branches: 70,
11
+ functions: 70,
12
+ lines: 70,
13
+ statements: 70,
14
+ },
15
+ },
16
+ testMatch: ['**/tests/**/*.test.js'],
17
+ setupFilesAfterEnv: ['./tests/setup.js'],
18
+ verbose: true,
19
+ forceExit: true,
20
+ clearMocks: true,
21
+ resetMocks: true,
22
+ restoreMocks: true,
23
+ testTimeout: 30000,
24
+ };
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "listablelabs-service",
3
+ "version": "1.0.0",
4
+ "description": "Microservice template - replace service-name with your service",
5
+ "main": "src/server.js",
6
+ "scripts": {
7
+ "start": "node src/server.js",
8
+ "dev": "nodemon src/server.js",
9
+ "test": "jest --coverage",
10
+ "test:watch": "jest --watch",
11
+ "lint": "eslint src/",
12
+ "lint:fix": "eslint src/ --fix",
13
+ "docker:build": "docker build -t service-name .",
14
+ "docker:run": "docker run -p 3000:3000 service-name"
15
+ },
16
+ "dependencies": {
17
+ "express": "^4.18.2",
18
+ "helmet": "^7.1.0",
19
+ "cors": "^2.8.5",
20
+ "compression": "^1.7.4",
21
+ "pino": "^8.17.2",
22
+ "pino-http": "^9.0.0",
23
+ "zod": "^3.22.4",
24
+ "dotenv": "^16.3.1",
25
+ "express-rate-limit": "^7.1.5",
26
+ "uuid": "^9.0.1",
27
+ "http-status-codes": "^2.3.0",
28
+ "mongoose": "^8.0.3"
29
+ },
30
+ "devDependencies": {
31
+ "nodemon": "^3.0.2",
32
+ "jest": "^29.7.0",
33
+ "supertest": "^6.3.3",
34
+ "eslint": "^8.56.0",
35
+ "pino-pretty": "^10.3.1",
36
+ "mongodb-memory-server": "^9.1.6"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ }
41
+ }
@@ -0,0 +1,103 @@
1
+ const express = require('express');
2
+ const helmet = require('helmet');
3
+ const cors = require('cors');
4
+ const compression = require('compression');
5
+ const config = require('./config');
6
+ const {
7
+ requestLogger,
8
+ errorHandler,
9
+ notFoundHandler,
10
+ defaultLimiter,
11
+ } = require('./middlewares');
12
+ const { healthCheck, getConnectionStatus } = require('./database');
13
+ const routes = require('./routes/v1');
14
+
15
+ const app = express();
16
+
17
+ /**
18
+ * Security middleware
19
+ */
20
+ app.use(helmet()); // Security headers
21
+ app.use(cors()); // CORS handling
22
+
23
+ /**
24
+ * Request parsing
25
+ */
26
+ app.use(express.json({ limit: '10kb' })); // Parse JSON bodies
27
+ app.use(express.urlencoded({ extended: true, limit: '10kb' })); // Parse URL-encoded bodies
28
+
29
+ /**
30
+ * Compression
31
+ */
32
+ app.use(compression());
33
+
34
+ /**
35
+ * Request logging - logs all incoming requests
36
+ */
37
+ app.use(requestLogger);
38
+
39
+ /**
40
+ * Rate limiting - applies to all routes
41
+ */
42
+ app.use(defaultLimiter);
43
+
44
+ /**
45
+ * Health check endpoint (liveness probe)
46
+ * Returns 200 if the service is running
47
+ */
48
+ app.get('/health', (req, res) => {
49
+ res.json({
50
+ status: 'healthy',
51
+ service: config.serviceName,
52
+ timestamp: new Date().toISOString(),
53
+ uptime: process.uptime(),
54
+ });
55
+ });
56
+
57
+ /**
58
+ * Readiness check endpoint (readiness probe)
59
+ * Returns 200 only if all dependencies are ready
60
+ */
61
+ app.get('/ready', async (req, res) => {
62
+ const dbHealthy = await healthCheck();
63
+
64
+ const status = {
65
+ service: config.serviceName,
66
+ timestamp: new Date().toISOString(),
67
+ checks: {
68
+ database: {
69
+ status: dbHealthy ? 'healthy' : 'unhealthy',
70
+ connection: getConnectionStatus(),
71
+ },
72
+ },
73
+ };
74
+
75
+ if (!dbHealthy) {
76
+ return res.status(503).json({
77
+ status: 'not ready',
78
+ ...status,
79
+ });
80
+ }
81
+
82
+ res.json({
83
+ status: 'ready',
84
+ ...status,
85
+ });
86
+ });
87
+
88
+ /**
89
+ * API routes
90
+ */
91
+ app.use('/api/v1', routes);
92
+
93
+ /**
94
+ * 404 handler for undefined routes
95
+ */
96
+ app.use(notFoundHandler);
97
+
98
+ /**
99
+ * Global error handler - must be last
100
+ */
101
+ app.use(errorHandler);
102
+
103
+ module.exports = app;
@@ -0,0 +1,36 @@
1
+ require('dotenv').config();
2
+
3
+ const config = {
4
+ // Application
5
+ env: process.env.NODE_ENV || 'development',
6
+ port: parseInt(process.env.PORT, 10) || 3000,
7
+ serviceName: process.env.SERVICE_NAME || 'unknown-service',
8
+
9
+ // Logging
10
+ logLevel: process.env.LOG_LEVEL || 'info',
11
+
12
+ // Rate Limiting
13
+ rateLimit: {
14
+ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000,
15
+ maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) || 100,
16
+ },
17
+
18
+ // MongoDB Atlas
19
+ mongoUri: process.env.MONGODB_URI,
20
+
21
+ // JWT (configure as needed)
22
+ // jwt: {
23
+ // secret: process.env.JWT_SECRET,
24
+ // expiresIn: process.env.JWT_EXPIRES_IN || '1d',
25
+ // },
26
+ };
27
+
28
+ // Validate required config
29
+ const requiredEnvVars = ['SERVICE_NAME', 'MONGODB_URI'];
30
+ const missingEnvVars = requiredEnvVars.filter((envVar) => !process.env[envVar]);
31
+
32
+ if (missingEnvVars.length > 0 && process.env.NODE_ENV !== 'test') {
33
+ throw new Error(`Missing required environment variables: ${missingEnvVars.join(', ')}`);
34
+ }
35
+
36
+ module.exports = config;
@@ -0,0 +1,148 @@
1
+ const { asyncHandler, NotFoundError } = require('../middlewares');
2
+ const { sendSuccess, sendCreated, sendPaginated, sendNoContent } = require('../utils/response');
3
+ const { createChildLogger } = require('../utils/logger');
4
+ const { Example } = require('../models');
5
+
6
+ const log = createChildLogger({ module: 'exampleController' });
7
+
8
+ /**
9
+ * Get all examples with pagination
10
+ * GET /api/v1/examples
11
+ */
12
+ const getAll = asyncHandler(async (req, res) => {
13
+ const { page, limit, status, search, sortBy, sortOrder } = req.query;
14
+
15
+ log.info({ page, limit, status, search }, 'Fetching examples');
16
+
17
+ const result = await Example.search(
18
+ { status, search },
19
+ { page, limit, sortBy, sortOrder }
20
+ );
21
+
22
+ sendPaginated(res, result.data, result.pagination);
23
+ });
24
+
25
+ /**
26
+ * Get example by ID
27
+ * GET /api/v1/examples/:id
28
+ */
29
+ const getById = asyncHandler(async (req, res) => {
30
+ const { id } = req.params;
31
+
32
+ log.info({ id }, 'Fetching example by ID');
33
+
34
+ const example = await Example.findById(id);
35
+
36
+ if (!example) {
37
+ throw new NotFoundError(`Example with ID ${id} not found`);
38
+ }
39
+
40
+ sendSuccess(res, example);
41
+ });
42
+
43
+ /**
44
+ * Create new example
45
+ * POST /api/v1/examples
46
+ */
47
+ const create = asyncHandler(async (req, res) => {
48
+ const data = req.body;
49
+
50
+ log.info({ data }, 'Creating new example');
51
+
52
+ const example = await Example.create(data);
53
+
54
+ sendCreated(res, example);
55
+ });
56
+
57
+ /**
58
+ * Update example
59
+ * PUT /api/v1/examples/:id
60
+ */
61
+ const update = asyncHandler(async (req, res) => {
62
+ const { id } = req.params;
63
+ const data = req.body;
64
+
65
+ log.info({ id, data }, 'Updating example');
66
+
67
+ const example = await Example.findByIdAndUpdate(
68
+ id,
69
+ data,
70
+ { new: true, runValidators: true }
71
+ );
72
+
73
+ if (!example) {
74
+ throw new NotFoundError(`Example with ID ${id} not found`);
75
+ }
76
+
77
+ sendSuccess(res, example, 200, 'Example updated successfully');
78
+ });
79
+
80
+ /**
81
+ * Delete example (soft delete)
82
+ * DELETE /api/v1/examples/:id
83
+ */
84
+ const remove = asyncHandler(async (req, res) => {
85
+ const { id } = req.params;
86
+
87
+ log.info({ id }, 'Deleting example');
88
+
89
+ const example = await Example.findById(id);
90
+
91
+ if (!example) {
92
+ throw new NotFoundError(`Example with ID ${id} not found`);
93
+ }
94
+
95
+ await example.softDelete();
96
+
97
+ sendNoContent(res);
98
+ });
99
+
100
+ /**
101
+ * Activate example
102
+ * PATCH /api/v1/examples/:id/activate
103
+ */
104
+ const activate = asyncHandler(async (req, res) => {
105
+ const { id } = req.params;
106
+
107
+ log.info({ id }, 'Activating example');
108
+
109
+ const example = await Example.findById(id);
110
+
111
+ if (!example) {
112
+ throw new NotFoundError(`Example with ID ${id} not found`);
113
+ }
114
+
115
+ await example.activate();
116
+
117
+ sendSuccess(res, example, 200, 'Example activated');
118
+ });
119
+
120
+ /**
121
+ * Deactivate example
122
+ * PATCH /api/v1/examples/:id/deactivate
123
+ */
124
+ const deactivate = asyncHandler(async (req, res) => {
125
+ const { id } = req.params;
126
+
127
+ log.info({ id }, 'Deactivating example');
128
+
129
+ const example = await Example.findById(id);
130
+
131
+ if (!example) {
132
+ throw new NotFoundError(`Example with ID ${id} not found`);
133
+ }
134
+
135
+ await example.deactivate();
136
+
137
+ sendSuccess(res, example, 200, 'Example deactivated');
138
+ });
139
+
140
+ module.exports = {
141
+ getAll,
142
+ getById,
143
+ create,
144
+ update,
145
+ remove,
146
+ activate,
147
+ deactivate,
148
+ };