ts-rails 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 (186) hide show
  1. package/README.md +442 -0
  2. package/dist/app/channels/index.d.ts +2 -0
  3. package/dist/app/channels/index.d.ts.map +1 -0
  4. package/dist/app/channels/index.js +18 -0
  5. package/dist/app/channels/index.js.map +1 -0
  6. package/dist/app/channels/railsChannel.d.ts +11 -0
  7. package/dist/app/channels/railsChannel.d.ts.map +1 -0
  8. package/dist/app/channels/railsChannel.js +20 -0
  9. package/dist/app/channels/railsChannel.js.map +1 -0
  10. package/dist/app/controllers/controllerHelpers.d.ts +4 -0
  11. package/dist/app/controllers/controllerHelpers.d.ts.map +1 -0
  12. package/dist/app/controllers/controllerHelpers.js +55 -0
  13. package/dist/app/controllers/controllerHelpers.js.map +1 -0
  14. package/dist/app/controllers/decorators.d.ts +4 -0
  15. package/dist/app/controllers/decorators.d.ts.map +1 -0
  16. package/dist/app/controllers/decorators.js +23 -0
  17. package/dist/app/controllers/decorators.js.map +1 -0
  18. package/dist/app/controllers/index.d.ts +7 -0
  19. package/dist/app/controllers/index.d.ts.map +1 -0
  20. package/dist/app/controllers/index.js +23 -0
  21. package/dist/app/controllers/index.js.map +1 -0
  22. package/dist/app/controllers/pagination.d.ts +29 -0
  23. package/dist/app/controllers/pagination.d.ts.map +1 -0
  24. package/dist/app/controllers/pagination.js +28 -0
  25. package/dist/app/controllers/pagination.js.map +1 -0
  26. package/dist/app/controllers/railsController.d.ts +30 -0
  27. package/dist/app/controllers/railsController.d.ts.map +1 -0
  28. package/dist/app/controllers/railsController.js +32 -0
  29. package/dist/app/controllers/railsController.js.map +1 -0
  30. package/dist/app/controllers/response.d.ts +19 -0
  31. package/dist/app/controllers/response.d.ts.map +1 -0
  32. package/dist/app/controllers/response.js +26 -0
  33. package/dist/app/controllers/response.js.map +1 -0
  34. package/dist/app/controllers/strongParams.d.ts +18 -0
  35. package/dist/app/controllers/strongParams.d.ts.map +1 -0
  36. package/dist/app/controllers/strongParams.js +91 -0
  37. package/dist/app/controllers/strongParams.js.map +1 -0
  38. package/dist/app/index.d.ts +7 -0
  39. package/dist/app/index.d.ts.map +1 -0
  40. package/dist/app/index.js +23 -0
  41. package/dist/app/index.js.map +1 -0
  42. package/dist/app/jobs/index.d.ts +2 -0
  43. package/dist/app/jobs/index.d.ts.map +1 -0
  44. package/dist/app/jobs/index.js +18 -0
  45. package/dist/app/jobs/index.js.map +1 -0
  46. package/dist/app/jobs/railsJob.d.ts +8 -0
  47. package/dist/app/jobs/railsJob.d.ts.map +1 -0
  48. package/dist/app/jobs/railsJob.js +15 -0
  49. package/dist/app/jobs/railsJob.js.map +1 -0
  50. package/dist/app/mailers/index.d.ts +2 -0
  51. package/dist/app/mailers/index.d.ts.map +1 -0
  52. package/dist/app/mailers/index.js +18 -0
  53. package/dist/app/mailers/index.js.map +1 -0
  54. package/dist/app/mailers/railsMailer.d.ts +13 -0
  55. package/dist/app/mailers/railsMailer.d.ts.map +1 -0
  56. package/dist/app/mailers/railsMailer.js +19 -0
  57. package/dist/app/mailers/railsMailer.js.map +1 -0
  58. package/dist/app/middlewares/fileUploadValidation.d.ts +6 -0
  59. package/dist/app/middlewares/fileUploadValidation.d.ts.map +1 -0
  60. package/dist/app/middlewares/fileUploadValidation.js +21 -0
  61. package/dist/app/middlewares/fileUploadValidation.js.map +1 -0
  62. package/dist/app/middlewares/index.d.ts +2 -0
  63. package/dist/app/middlewares/index.d.ts.map +1 -0
  64. package/dist/app/middlewares/index.js +18 -0
  65. package/dist/app/middlewares/index.js.map +1 -0
  66. package/dist/app/views/index.d.ts +2 -0
  67. package/dist/app/views/index.d.ts.map +1 -0
  68. package/dist/app/views/index.js +18 -0
  69. package/dist/app/views/index.js.map +1 -0
  70. package/dist/app/views/viewHelpers.d.ts +7 -0
  71. package/dist/app/views/viewHelpers.d.ts.map +1 -0
  72. package/dist/app/views/viewHelpers.js +42 -0
  73. package/dist/app/views/viewHelpers.js.map +1 -0
  74. package/dist/cli.d.ts +3 -0
  75. package/dist/cli.d.ts.map +1 -0
  76. package/dist/cli.js +111 -0
  77. package/dist/cli.js.map +1 -0
  78. package/dist/commands/console.d.ts +2 -0
  79. package/dist/commands/console.d.ts.map +1 -0
  80. package/dist/commands/console.js +85 -0
  81. package/dist/commands/console.js.map +1 -0
  82. package/dist/commands/generate-concern.d.ts +2 -0
  83. package/dist/commands/generate-concern.d.ts.map +1 -0
  84. package/dist/commands/generate-concern.js +40 -0
  85. package/dist/commands/generate-concern.js.map +1 -0
  86. package/dist/commands/generate-controller.d.ts +2 -0
  87. package/dist/commands/generate-controller.d.ts.map +1 -0
  88. package/dist/commands/generate-controller.js +92 -0
  89. package/dist/commands/generate-controller.js.map +1 -0
  90. package/dist/commands/generate-factory.d.ts +2 -0
  91. package/dist/commands/generate-factory.d.ts.map +1 -0
  92. package/dist/commands/generate-factory.js +43 -0
  93. package/dist/commands/generate-factory.js.map +1 -0
  94. package/dist/commands/generate-job.d.ts +2 -0
  95. package/dist/commands/generate-job.d.ts.map +1 -0
  96. package/dist/commands/generate-job.js +41 -0
  97. package/dist/commands/generate-job.js.map +1 -0
  98. package/dist/commands/generate-mailer.d.ts +2 -0
  99. package/dist/commands/generate-mailer.d.ts.map +1 -0
  100. package/dist/commands/generate-mailer.js +46 -0
  101. package/dist/commands/generate-mailer.js.map +1 -0
  102. package/dist/commands/generate-model.d.ts +2 -0
  103. package/dist/commands/generate-model.d.ts.map +1 -0
  104. package/dist/commands/generate-model.js +53 -0
  105. package/dist/commands/generate-model.js.map +1 -0
  106. package/dist/commands/generate-resource.d.ts +2 -0
  107. package/dist/commands/generate-resource.d.ts.map +1 -0
  108. package/dist/commands/generate-resource.js +62 -0
  109. package/dist/commands/generate-resource.js.map +1 -0
  110. package/dist/commands/generate-scaffold.d.ts +2 -0
  111. package/dist/commands/generate-scaffold.d.ts.map +1 -0
  112. package/dist/commands/generate-scaffold.js +183 -0
  113. package/dist/commands/generate-scaffold.js.map +1 -0
  114. package/dist/commands/generate-service.d.ts +2 -0
  115. package/dist/commands/generate-service.d.ts.map +1 -0
  116. package/dist/commands/generate-service.js +49 -0
  117. package/dist/commands/generate-service.js.map +1 -0
  118. package/dist/commands/notes.d.ts +2 -0
  119. package/dist/commands/notes.d.ts.map +1 -0
  120. package/dist/commands/notes.js +58 -0
  121. package/dist/commands/notes.js.map +1 -0
  122. package/dist/commands/routes.d.ts +2 -0
  123. package/dist/commands/routes.d.ts.map +1 -0
  124. package/dist/commands/routes.js +76 -0
  125. package/dist/commands/routes.js.map +1 -0
  126. package/dist/configs/cache.d.ts +15 -0
  127. package/dist/configs/cache.d.ts.map +1 -0
  128. package/dist/configs/cache.js +27 -0
  129. package/dist/configs/cache.js.map +1 -0
  130. package/dist/configs/index.d.ts +6 -0
  131. package/dist/configs/index.d.ts.map +1 -0
  132. package/dist/configs/index.js +22 -0
  133. package/dist/configs/index.js.map +1 -0
  134. package/dist/configs/logger.d.ts +12 -0
  135. package/dist/configs/logger.d.ts.map +1 -0
  136. package/dist/configs/logger.js +23 -0
  137. package/dist/configs/logger.js.map +1 -0
  138. package/dist/configs/railsApplication.d.ts +54 -0
  139. package/dist/configs/railsApplication.d.ts.map +1 -0
  140. package/dist/configs/railsApplication.js +314 -0
  141. package/dist/configs/railsApplication.js.map +1 -0
  142. package/dist/configs/railsRoute.d.ts +71 -0
  143. package/dist/configs/railsRoute.d.ts.map +1 -0
  144. package/dist/configs/railsRoute.js +332 -0
  145. package/dist/configs/railsRoute.js.map +1 -0
  146. package/dist/configs/swagger/builder.d.ts +20 -0
  147. package/dist/configs/swagger/builder.d.ts.map +1 -0
  148. package/dist/configs/swagger/builder.js +81 -0
  149. package/dist/configs/swagger/builder.js.map +1 -0
  150. package/dist/configs/swagger/decorator.d.ts +23 -0
  151. package/dist/configs/swagger/decorator.d.ts.map +1 -0
  152. package/dist/configs/swagger/decorator.js +27 -0
  153. package/dist/configs/swagger/decorator.js.map +1 -0
  154. package/dist/configs/swagger/index.d.ts +5 -0
  155. package/dist/configs/swagger/index.d.ts.map +1 -0
  156. package/dist/configs/swagger/index.js +21 -0
  157. package/dist/configs/swagger/index.js.map +1 -0
  158. package/dist/configs/swagger/registry.d.ts +4 -0
  159. package/dist/configs/swagger/registry.d.ts.map +1 -0
  160. package/dist/configs/swagger/registry.js +22 -0
  161. package/dist/configs/swagger/registry.js.map +1 -0
  162. package/dist/configs/swagger/server.d.ts +3 -0
  163. package/dist/configs/swagger/server.d.ts.map +1 -0
  164. package/dist/configs/swagger/server.js +17 -0
  165. package/dist/configs/swagger/server.js.map +1 -0
  166. package/dist/index.d.ts +4 -0
  167. package/dist/index.d.ts.map +1 -0
  168. package/dist/index.js +20 -0
  169. package/dist/index.js.map +1 -0
  170. package/dist/lib/index.d.ts +2 -0
  171. package/dist/lib/index.d.ts.map +1 -0
  172. package/dist/lib/index.js +18 -0
  173. package/dist/lib/index.js.map +1 -0
  174. package/dist/lib/utils/errors.d.ts +22 -0
  175. package/dist/lib/utils/errors.d.ts.map +1 -0
  176. package/dist/lib/utils/errors.js +45 -0
  177. package/dist/lib/utils/errors.js.map +1 -0
  178. package/dist/lib/utils/index.d.ts +3 -0
  179. package/dist/lib/utils/index.d.ts.map +1 -0
  180. package/dist/lib/utils/index.js +19 -0
  181. package/dist/lib/utils/index.js.map +1 -0
  182. package/dist/lib/utils/security.d.ts +9 -0
  183. package/dist/lib/utils/security.d.ts.map +1 -0
  184. package/dist/lib/utils/security.js +14 -0
  185. package/dist/lib/utils/security.js.map +1 -0
  186. package/package.json +105 -0
package/README.md ADDED
@@ -0,0 +1,442 @@
1
+ # ts-rails 🚂
2
+
3
+ **The Core Engine for TS Rails.**
4
+ Hệ thống lõi cung cấp các tính năng mạnh mẽ tương tự Ruby on Rails cho hệ sinh thái Node.js, sử dụng TypeScript và Express.
5
+
6
+ ---
7
+
8
+ ## 🌟 Tính năng nổi bật
9
+
10
+ - 🚀 **Application**: Tích hợp sâu vào lõi Express.
11
+ - 🛣 **Routing Siêu Tốc**: Hỗ trợ `resource()` và helper `action()` để định nghĩa route nhanh chóng.
12
+ - 🎮 **Controller Decorators**: Sử dụng `@BeforeAction`, `@AfterAction` để quản lý luồng xử lý (Filters).
13
+ - 🔌 **Real-time Channels**: Tích hợp Socket.IO thông qua `RailsChannel` (giống ActionCable).
14
+ - 📮 **Unified Mailers**: Interface gửi mail đồng nhất với hệ thống Adapter linh hoạt.
15
+ - 📝 **Auto Swagger**: Tự động tạo tài liệu OpenAPI dựa trên khai báo route và validators.
16
+ - 🔢 **Pagination & Response**: Chuẩn hóa dữ liệu trả về và hỗ trợ phân trang chuẩn mực.
17
+ - 🖼 **View Helpers**: Hỗ trợ mạnh mẽ cho Pug/EJS với định dạng tiền tệ, thời gian và Vite assets.
18
+ - 👷 **Background Jobs**: Định nghĩa job chạy nền đồng nhất cho cả BullMQ và AWS Lambda.
19
+ - 🛠 **CLI Scaffolding**: Bộ generator mạnh mẽ giúp tạo code nhanh chóng.
20
+
21
+ ---
22
+
23
+ ## 📦 Cài đặt
24
+
25
+ Cài đặt thông qua các package manager yêu thích của bạn:
26
+
27
+ ```bash
28
+ # npm
29
+ npm install ts-rails
30
+
31
+ # yarn
32
+ yarn add ts-rails
33
+
34
+ # pnpm
35
+ pnpm add ts-rails
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 🛠 Các thành phần chính
41
+
42
+ ### 1. RailsApplication Core 🚂
43
+
44
+ `RailsApplication` là trái tim của hệ thống, quản lý toàn bộ vòng đời ứng dụng (Lifecycle), từ việc thiết lập cấu hình, nạp Initializers, quản lý Middleware cho đến khởi chạy Server và Socket.IO.
45
+
46
+ #### 1. Khởi tạo Application Class
47
+
48
+ Để bắt đầu, bạn cần kế thừa lớp `RailsApplication`. Đây là nơi bạn định nghĩa cách ứng dụng của mình khởi động.
49
+
50
+ ```typescript
51
+ import { RailsApplication } from "ts-rails";
52
+ import { Route } from "./routes";
53
+
54
+ export class Application extends RailsApplication {
55
+ constructor() {
56
+ super();
57
+ this.port = process.env.PORT || "8000";
58
+ }
59
+
60
+ // Gắn Router vào Express
61
+ protected mountRoutes() {
62
+ this.app.use("/", Route.draw());
63
+ }
64
+
65
+ // Khởi tạo các dịch vụ cần thiết (Database, Redis, etc.)
66
+ public async initialize() {
67
+ this.setupAppMiddlewares();
68
+ await this.setupServices();
69
+ this.bootstrap(); // Thiết lập server HTTP và Socket.IO
70
+ }
71
+ }
72
+ ```
73
+
74
+ #### 2. Middleware Factory Configuration
75
+
76
+ Framework cung cấp một `MiddlewareFactory` toàn cục để bạn có thể ghi đè hoặc bổ sung các middleware hệ thống (như Rate Limit, Request Logging) bằng logic tùy chỉnh của mình.
77
+
78
+ ```typescript
79
+ import { RailsApplication, MiddlewareFactory } from "ts-rails";
80
+ import { rateLimitMiddleware } from "@middlewares/rateLimit.middleware";
81
+ import { requestIdMiddleware } from "@middlewares/requestId.middleware";
82
+
83
+ RailsApplication.middlewareFactory = {
84
+ rateLimit: rateLimitMiddleware,
85
+ requestId: () => requestIdMiddleware,
86
+ requestLogging: () => (req, res, next) => {
87
+ console.log(`${req.method} ${req.url}`);
88
+ next();
89
+ },
90
+ } as MiddlewareFactory;
91
+ ```
92
+
93
+ #### 3. Quản lý Controller Concerns (Mixins)
94
+
95
+ Tương tự như Ruby on Rails, bạn có thể sử dụng `loadConcerns` để "nhúng" các hàm dùng chung vào Controller mà không cần kế thừa phức tạp.
96
+
97
+ ```typescript
98
+ import { ApplicationController } from "@controllers/application.controller";
99
+
100
+ // Trong phương thức initialize() của Application class:
101
+ this.loadConcerns(
102
+ ApplicationController.prototype,
103
+ "app/controllers/concerns", // Đường dẫn tới thư mục chứa các file .ts concern
104
+ );
105
+ ```
106
+
107
+ _Ví dụ: Nếu bạn có file `rescuable.ts` trong thư mục concerns, các phương thức trong đó sẽ tự động xuất hiện trong `ApplicationController`._
108
+
109
+ #### 4. Socket.IO Channels
110
+
111
+ Đăng ký tất cả các lớp Channel để sử dụng tính năng Real-time:
112
+
113
+ ```typescript
114
+ import * as channels from "@channels";
115
+
116
+ RailsApplication.channelClasses = Object.values(channels);
117
+ ```
118
+
119
+ #### 5. Background Processing (BullMQ)
120
+
121
+ Framework hỗ trợ xử lý tác vụ chạy nền. Bạn có thể override `startBackgroundProcessor` để khởi chạy Worker:
122
+
123
+ ```typescript
124
+ protected startBackgroundProcessor() {
125
+ // Logic setup BullMQ Worker hoặc các trình xử lý hàng đợi khác
126
+ setupBullMQWorker();
127
+ }
128
+ ```
129
+
130
+ #### 6. Vòng đời khởi chạy (Full Lifecycle)
131
+
132
+ Dưới đây là thứ tự chuẩn để chạy ứng dụng:
133
+
134
+ ```typescript
135
+ export class Application extends RailsApplication {
136
+ // ... các phương thức setupConfig, setupViewEngine ...
137
+
138
+ public async run() {
139
+ // 1. Chạy các Initializers (Logger, Mailer, Cache...)
140
+ initializeLogger();
141
+ initializeMailer();
142
+
143
+ // 2. Setup view engine & static files
144
+ this.app.set("view engine", "pug");
145
+
146
+ // 3. Khởi tạo hệ thống
147
+ await this.initialize();
148
+
149
+ // 4. Start Server
150
+ super.run();
151
+ }
152
+ }
153
+
154
+ const app = new Application();
155
+ app.run();
156
+ ```
157
+
158
+ #### 📄 Ghi chú
159
+
160
+ Mọi cấu hình mặc định có thể được tùy chỉnh thông qua việc override các phương thức `protected` trong lớp `RailsApplication`.
161
+
162
+ ### 2. Resourceful Routing & `action()`
163
+
164
+ Không còn phải khai báo từng route thủ công. Kết nối trực tiếp Route với Controller Class:
165
+
166
+ ```typescript
167
+ import { RailsRoute, action } from "ts-rails";
168
+ import { UsersController } from "../controllers/users.controller";
169
+
170
+ export class AppRoute extends RailsRoute {
171
+ draw(): void {
172
+ / Tạo ra 7 routes tiêu chuẩn (index, show, create, update, destroy, ...)
173
+ this.resource("/users", UsersController, {
174
+ only: ["index", "show", "create"],
175
+ setPermissionFor: "USER_MANAGEMENT",
176
+ });
177
+
178
+ // Sử dụng helper action() cho các route tùy chỉnh
179
+ this.get("/profile", action(UsersController, "profile"));
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### 4. Strong Parameters & Validation
185
+
186
+ Thư viện hỗ trợ cơ chế bảo mật tham số đầu vào (Whitelist) kết hợp với `class-validator` để tự động kiểm tra và chuyển đổi kiểu dữ liệu.
187
+
188
+ #### Bước 1: Định nghĩa Validator Class
189
+
190
+ ```typescript
191
+ import { IsEmail, IsString, MinLength, IsOptional } from "class-validator";
192
+
193
+ export class CreateUserValidator {
194
+ @IsEmail({}, { message: "Email không hợp lệ" })
195
+ email: string;
196
+
197
+ @IsString()
198
+ @MinLength(3, { message: "Tên phải có ít nhất 3 ký tự" })
199
+ name: string;
200
+
201
+ @IsString()
202
+ @IsOptional()
203
+ bio?: string;
204
+ }
205
+ ```
206
+
207
+ #### Bước 2: Sử dụng trong Controller
208
+
209
+ ```typescript
210
+ import { RailsController } from "ts-rails";
211
+ import { CreateUserValidator } from "../validators/user.validator";
212
+
213
+ export class UsersController extends RailsController {
214
+ async create() {
215
+ /**
216
+ * this.params(Validator) thực hiện:
217
+ * 1. Validate dữ liệu từ request body/query dựa trên class-validator.
218
+ * 2. Tự động trả về lỗi 422 nếu dữ liệu không hợp lệ.
219
+ * 3. .permit(...) whitelist các trường được phép nhận.
220
+ */
221
+ const userParams = await this.params(CreateUserValidator).permit(
222
+ "email",
223
+ "name",
224
+ "bio",
225
+ );
226
+
227
+ // userParams đã được validate và lọc sạch
228
+ const user = await UserService.create(userParams);
229
+ return this.renderJson(user);
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### 5. Controller Filters & Decorators
235
+
236
+ Quản lý logic tiền xử lý và hậu xử lý cực kỳ dễ dàng:
237
+
238
+ ```typescript
239
+ import { RailsController, BeforeAction, AfterAction } from "ts-rails";
240
+
241
+ @BeforeAction("authenticate", { except: ["index"] })
242
+ @AfterAction("logActivity", { only: ["create", "update"] })
243
+ export class UsersController extends RailsController {
244
+ async authenticate() {
245
+ if (!this.req.session.userId) return this.renderError(401, "Unauthorized");
246
+ }
247
+
248
+ // ... logic xử lý các action khác
249
+ }
250
+ ```
251
+
252
+ ### 6. File Upload Validation
253
+
254
+ Tích hợp sẵn middleware xử lý upload và kiểm tra ràng buộc file ngay trong cấu hình route.
255
+
256
+ ```typescript
257
+ this.resource("/products", ProductsController, {
258
+ upload: {
259
+ fields: [
260
+ { name: "thumbnail", maxCount: 1 },
261
+ { name: "gallery", maxCount: 5 },
262
+ ],
263
+ limits: {
264
+ fileSize: 5 * 1024 * 1024, // Giới hạn 5MB cho mỗi file
265
+ },
266
+ fileFilter: (req, file, cb) => {
267
+ // Chỉ cho phép file ảnh
268
+ if (!file.mimetype.startsWith("image/")) {
269
+ return cb(new Error("Chỉ chấp nhận định dạng hình ảnh!"), false);
270
+ }
271
+ cb(null, true);
272
+ },
273
+ },
274
+ });
275
+ ```
276
+
277
+ ### 7. Real-time Channels (Socket.IO)
278
+
279
+ Khai báo channel theo hướng đối tượng:
280
+
281
+ ```typescript
282
+ import { RailsChannel } from "ts-rails";
283
+
284
+ export class ChatChannel extends RailsChannel {
285
+ subscribe() {
286
+ this.on("message", (data) => {
287
+ this.broadcast("room_1", { content: data });
288
+ });
289
+ }
290
+ }
291
+ ```
292
+
293
+ ### 8. View Helpers (Time, Currency, Vite)
294
+
295
+ Cung cấp sẵn bộ `viewHelpers` để sử dụng trong các template engine:
296
+
297
+ ```typescript
298
+ import { viewHelpers as h } from "ts-rails";
299
+
300
+ h.timeAgo(Date.now()); // "a few seconds ago"
301
+ h.numberToCurrency(50000); // "50.000 ₫"
302
+ h.assetPath("main.ts"); // Tự động nhận diện path từ Vite manifest
303
+ ```
304
+
305
+ ### 9. Mailers & Custom Adapters
306
+
307
+ Hệ thống Mailer hoạt động theo mô hình Adapter, cho phép bạn linh hoạt thay đổi dịch vụ gửi mail mà không làm thay đổi code business.
308
+
309
+ #### Bước 1: Triển khai Adapter (Ví dụ: Gmail OAuth2)
310
+
311
+ ```typescript
312
+ import { MailerAdapter } from "ts-rails";
313
+ import { createTransport } from "nodemailer";
314
+
315
+ export class GmailOAuth2Adapter implements MailerAdapter {
316
+ async sendMail(options: any): Promise<void> {
317
+ const transporter = createTransport({
318
+ service: "gmail",
319
+ auth: {
320
+ type: "OAuth2",
321
+ user: process.env.EMAIL_FROM,
322
+ clientId: process.env.GOOGLE_CLIENT_ID,
323
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
324
+ refreshToken: process.env.GOOGLE_REFRESH_TOKEN,
325
+ },
326
+ });
327
+
328
+ await transporter.sendMail({
329
+ ...options,
330
+ from: options.from || this.getDefaultFromAddress(),
331
+ });
332
+ }
333
+
334
+ getDefaultFromAddress(): string {
335
+ return `TS Rails App <${process.env.EMAIL_FROM}>`;
336
+ }
337
+ }
338
+ ```
339
+
340
+ #### Bước 2: Đăng ký Adapter trong Initializer
341
+
342
+ ```typescript
343
+ import { RailsApplication } from "ts-rails";
344
+ import { GmailOAuth2Adapter } from "./mail/gmail-oauth2.adapter";
345
+
346
+ // Cấu hình thường đặt trong configs/initializers/mailer.ts
347
+ RailsApplication.mailerAdapter = new GmailOAuth2Adapter();
348
+ ```
349
+
350
+ #### Bước 3: Định nghĩa và sử dụng Mailer
351
+
352
+ ```typescript
353
+ import { RailsMailer } from "ts-rails";
354
+
355
+ export class UserMailer extends RailsMailer {
356
+ static async welcome(user: any) {
357
+ await this.deliver({
358
+ to: user.email,
359
+ subject: "Welcome to TS Rails!",
360
+ template: "mailers/welcome",
361
+ locals: { name: user.name },
362
+ });
363
+ }
364
+ }
365
+ ```
366
+
367
+ ### 10. Pagination & Standardized Response
368
+
369
+ ```typescript
370
+ import { parsePagination, buildPaginatedResponse } from "ts-rails";
371
+
372
+ async index() {
373
+ const { page, perPage, skip } = parsePagination(this.req.query);
374
+ const [data, total] = await db.user.findManyAndCount({ skip, take: perPage });
375
+
376
+ return this.renderJson(buildPaginatedResponse(data, total, { page, perPage }));
377
+ }
378
+ ```
379
+
380
+ ---
381
+
382
+ ## ⚙️ Cấu hình & Tiện ích
383
+
384
+ ### Swagger Configuration
385
+
386
+ Tự động quét và tạo tài liệu API:
387
+
388
+ ```typescript
389
+ this.resource("/api/users", UsersController, {
390
+ document: {
391
+ body: CreateUserValidator,
392
+ summary: "Tạo người dùng mới",
393
+ tags: ["User Management"],
394
+ },
395
+ });
396
+ ```
397
+
398
+ ### Logger & Cache
399
+
400
+ Sử dụng interface đồng nhất cho toàn hệ thống:
401
+
402
+ ```typescript
403
+ import { logger } from "ts-rails";
404
+ import { Cache } from "ts-rails";
405
+
406
+ logger.info("Server started");
407
+ await Cache.set("key", value, { ttl: 3600 });
408
+ ```
409
+
410
+ ---
411
+
412
+ ## 💻 CLI Commands
413
+
414
+ Sử dụng command `rails` để sinh mã nguồn nhanh chóng theo chuẩn `field:type`.
415
+
416
+ **Cú pháp:**
417
+ `npx rails generate <loại> <TênModel> [field:type field:type ...]`
418
+
419
+ ### Các lệnh chính:
420
+
421
+ | Lệnh | Mô tả | Ví dụ |
422
+ | :------------- | :-------------------------------------------- | :----------------------------------------------------------------- |
423
+ | `g scaffold` | Tạo trọn bộ: Model, Controller, Views, Routes | `npx rails g scaffold Product name:string price:integer desc:text` |
424
+ | `g resource` | Tạo API Resource (Controller + Model) | `npx rails g resource Order total:decimal status:string` |
425
+ | `g controller` | Tạo Controller mới | `npx rails g controller Admin/Dashboard` |
426
+ | `g service` | Tạo Service xử lý logic | `npx rails g service Payment` |
427
+ | `g mailer` | Tạo class Mailer | `npx rails g mailer Notification` |
428
+ | `g job` | Tạo Background Job | `npx rails g job SyncData` |
429
+ | `g channel` | Tạo Socket.IO Channel | `npx rails g channel Chat` |
430
+
431
+ **Các kiểu dữ liệu hỗ trợ:**
432
+ `string`, `text`, `integer`, `float`, `decimal`, `boolean`, `date`, `datetime`, `json`.
433
+
434
+ ---
435
+
436
+ ## 📄 License
437
+
438
+ MIT
439
+
440
+ ---
441
+
442
+ _Phát triển bởi Hoan Pham và cộng đồng._
@@ -0,0 +1,2 @@
1
+ export * from "./railsChannel";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../app/channels/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./railsChannel"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../app/channels/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B"}
@@ -0,0 +1,11 @@
1
+ import { Socket, Server as SocketServer } from "socket.io";
2
+ export declare abstract class RailsChannel {
3
+ protected io: SocketServer;
4
+ protected socket: Socket;
5
+ constructor(io: SocketServer, socket: Socket);
6
+ abstract subscribe(): void;
7
+ protected join(room: string): void;
8
+ protected leave(room: string): void;
9
+ protected broadcastTo(room: string, event: string, ...data: any[]): void;
10
+ }
11
+ //# sourceMappingURL=railsChannel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"railsChannel.d.ts","sourceRoot":"","sources":["../../../app/channels/railsChannel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,WAAW,CAAC;AAE3D,8BAAsB,YAAY;IAChC,SAAS,CAAC,EAAE,EAAE,YAAY,CAAC;IAC3B,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEb,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM;IAc5C,QAAQ,CAAC,SAAS,IAAI,IAAI;IAM1B,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM;IAQ3B,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM;IAU5B,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;CAGlE"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RailsChannel = void 0;
4
+ class RailsChannel {
5
+ constructor(io, socket) {
6
+ this.io = io;
7
+ this.socket = socket;
8
+ }
9
+ join(room) {
10
+ this.socket.join(room);
11
+ }
12
+ leave(room) {
13
+ this.socket.leave(room);
14
+ }
15
+ broadcastTo(room, event, ...data) {
16
+ this.io.to(room).emit(event, ...data);
17
+ }
18
+ }
19
+ exports.RailsChannel = RailsChannel;
20
+ //# sourceMappingURL=railsChannel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"railsChannel.js","sourceRoot":"","sources":["../../../app/channels/railsChannel.ts"],"names":[],"mappings":";;;AAEA,MAAsB,YAAY;IAIhC,YAAY,EAAgB,EAAE,MAAc;QAC1C,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAiBS,IAAI,CAAC,IAAY;QACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAMS,KAAK,CAAC,IAAY;QAC1B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAQS,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,GAAG,IAAW;QAC/D,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IACxC,CAAC;CACF;AA7CD,oCA6CC"}
@@ -0,0 +1,4 @@
1
+ import { RequestHandler } from "express";
2
+ export declare function action(Klass: new (...args: any[]) => any, actionName: string): RequestHandler;
3
+ export declare function action(Klass: new (...args: any[]) => any): RequestHandler;
4
+ //# sourceMappingURL=controllerHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controllerHelpers.d.ts","sourceRoot":"","sources":["../../../app/controllers/controllerHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAS1E,wBAAgB,MAAM,CACpB,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAClC,UAAU,EAAE,MAAM,GACjB,cAAc,CAAC;AAClB,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,cAAc,CAAC"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.action = action;
4
+ function action(Klass, actionName = "execute") {
5
+ return async (req, res, next) => {
6
+ const instance = new Klass();
7
+ instance.req = req;
8
+ instance.res = res;
9
+ try {
10
+ const beforeResult = await runFilters(Klass, instance, actionName, "beforeActions");
11
+ if (beforeResult === false) {
12
+ return;
13
+ }
14
+ try {
15
+ if (actionName === "execute" &&
16
+ typeof instance.execute === "function" &&
17
+ instance.execute.length >= 3) {
18
+ await instance.execute(req, res, next);
19
+ }
20
+ else {
21
+ await instance[actionName]();
22
+ }
23
+ }
24
+ finally {
25
+ await runFilters(Klass, instance, actionName, "afterActions");
26
+ }
27
+ }
28
+ catch (error) {
29
+ next(error);
30
+ }
31
+ };
32
+ }
33
+ async function runFilters(Klass, instance, actionName, filterType) {
34
+ const filters = Klass[filterType] || [];
35
+ for (const filter of filters) {
36
+ const handlerName = filter.handler;
37
+ const options = filter.options;
38
+ const only = options === null || options === void 0 ? void 0 : options.only;
39
+ const except = options === null || options === void 0 ? void 0 : options.except;
40
+ const shouldRun = actionName !== "execute" &&
41
+ ((!only && !except) ||
42
+ (only && only.includes(actionName)) ||
43
+ (except && !except.includes(actionName)));
44
+ if (shouldRun) {
45
+ if (typeof instance[handlerName] !== "function") {
46
+ throw new Error(`Filter handler "${handlerName}" is not a valid method on ${Klass.name}`);
47
+ }
48
+ const result = await instance[handlerName]();
49
+ if (filterType === "beforeActions" && result === false)
50
+ return false;
51
+ }
52
+ }
53
+ return true;
54
+ }
55
+ //# sourceMappingURL=controllerHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controllerHelpers.js","sourceRoot":"","sources":["../../../app/controllers/controllerHelpers.ts"],"names":[],"mappings":";;AAoBA,wBAyCC;AAzCD,SAAgB,MAAM,CACpB,KAAkC,EAClC,UAAU,GAAG,SAAS;IAEtB,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,MAAM,QAAQ,GAAG,IAAI,KAAK,EAAE,CAAC;QAC5B,QAAgB,CAAC,GAAG,GAAG,GAAG,CAAC;QAC3B,QAAgB,CAAC,GAAG,GAAG,GAAG,CAAC;QAE5B,IAAI,CAAC;YAEH,MAAM,YAAY,GAAG,MAAM,UAAU,CACnC,KAAK,EACL,QAAQ,EACR,UAAU,EACV,eAAe,CAChB,CAAC;YACF,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;gBAC3B,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBAEH,IACE,UAAU,KAAK,SAAS;oBACxB,OAAO,QAAQ,CAAC,OAAO,KAAK,UAAU;oBACtC,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,EAC5B,CAAC;oBACD,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;gBACzC,CAAC;qBAAM,CAAC;oBACN,MAAO,QAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;gBACxC,CAAC;YACH,CAAC;oBAAS,CAAC;gBAGT,MAAM,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,KAAU,EACV,QAAa,EACb,UAAkB,EAClB,UAA4C;IAE5C,MAAM,OAAO,GACX,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAE1B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC;QACnC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAmD,CAAC;QAC3E,MAAM,IAAI,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,CAAC;QAE/B,MAAM,SAAS,GACb,UAAU,KAAK,SAAS;YACxB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC;gBACjB,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBACnC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAE9C,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,OAAQ,QAAgB,CAAC,WAAW,CAAC,KAAK,UAAU,EAAE,CAAC;gBACzD,MAAM,IAAI,KAAK,CACb,mBAAmB,WAAW,8BAA8B,KAAK,CAAC,IAAI,EAAE,CACzE,CAAC;YACJ,CAAC;YACD,MAAM,MAAM,GAAG,MAAO,QAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;YACtD,IAAI,UAAU,KAAK,eAAe,IAAI,MAAM,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;QACvE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { AfterActionOptions, BeforeActionOptions } from "./railsController";
2
+ export declare function BeforeAction(handler: string, options?: BeforeActionOptions): (constructor: Function) => void;
3
+ export declare function AfterAction(handler: string, options?: AfterActionOptions): (constructor: Function) => void;
4
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../../app/controllers/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAM5E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,IACxD,aAAa,QAAQ,UAQvC;AAMD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,IACtD,aAAa,QAAQ,UAQvC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BeforeAction = BeforeAction;
4
+ exports.AfterAction = AfterAction;
5
+ function BeforeAction(handler, options) {
6
+ return function (constructor) {
7
+ const target = constructor;
8
+ if (!Object.prototype.hasOwnProperty.call(target, "beforeActions")) {
9
+ target.beforeActions = [...(target.beforeActions || [])];
10
+ }
11
+ target.beforeActions.push({ handler, options });
12
+ };
13
+ }
14
+ function AfterAction(handler, options) {
15
+ return function (constructor) {
16
+ const target = constructor;
17
+ if (!Object.prototype.hasOwnProperty.call(target, "afterActions")) {
18
+ target.afterActions = [...(target.afterActions || [])];
19
+ }
20
+ target.afterActions.push({ handler, options });
21
+ };
22
+ }
23
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../app/controllers/decorators.ts"],"names":[],"mappings":";;AAMA,oCASC;AAMD,kCASC;AAxBD,SAAgB,YAAY,CAAC,OAAe,EAAE,OAA6B;IACzE,OAAO,UAAU,WAAqB;QACpC,MAAM,MAAM,GAAG,WAAkB,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC;YAEnE,MAAM,CAAC,aAAa,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC;AAMD,SAAgB,WAAW,CAAC,OAAe,EAAE,OAA4B;IACvE,OAAO,UAAU,WAAqB;QACpC,MAAM,MAAM,GAAG,WAAkB,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,CAAC;YAElE,MAAM,CAAC,YAAY,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACjD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from "./controllerHelpers";
2
+ export * from "./decorators";
3
+ export * from "./pagination";
4
+ export * from "./railsController";
5
+ export * from "./response";
6
+ export * from "./strongParams";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../app/controllers/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./controllerHelpers"), exports);
18
+ __exportStar(require("./decorators"), exports);
19
+ __exportStar(require("./pagination"), exports);
20
+ __exportStar(require("./railsController"), exports);
21
+ __exportStar(require("./response"), exports);
22
+ __exportStar(require("./strongParams"), exports);
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../app/controllers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,+CAA6B;AAC7B,+CAA6B;AAC7B,oDAAkC;AAClC,6CAA2B;AAC3B,iDAA+B"}