mega-framework 0.1.5 → 0.1.7

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 (236) hide show
  1. package/bin/mega-ws-hub.js +2 -2
  2. package/package.json +32 -8
  3. package/sample/crud/.env +156 -8
  4. package/sample/crud/.env.example +153 -28
  5. package/sample/crud/.mega/journal/history/20260612092543-create-users.json +261 -0
  6. package/sample/crud/.mega/journal/snapshot.json +261 -0
  7. package/sample/crud/apps/main/controllers/auth-controller.js +22 -14
  8. package/sample/crud/apps/main/controllers/web-controller.js +7 -5
  9. package/sample/crud/apps/main/migrations/20260606000001-create-users.js +91 -13
  10. package/sample/crud/apps/main/migrations/20260606000002-create-boards.js +165 -0
  11. package/sample/crud/apps/main/migrations/20260606000003-create-logs.js +107 -0
  12. package/sample/crud/apps/main/models/log-partition-model.js +105 -0
  13. package/sample/crud/apps/main/models/note-model.js +79 -0
  14. package/sample/crud/apps/main/models/user-level-model.js +24 -0
  15. package/sample/crud/apps/main/models/user-model.js +146 -0
  16. package/sample/crud/apps/main/models/user-type-model.js +21 -0
  17. package/sample/crud/apps/main/models/wallet-model.js +24 -0
  18. package/sample/crud/apps/main/routes/users.js +55 -10
  19. package/sample/crud/apps/main/schedules/log-partition-schedule.js +33 -0
  20. package/sample/crud/apps/main/services/auth-service.js +39 -24
  21. package/sample/crud/apps/main/services/log-partition-service.js +101 -0
  22. package/sample/crud/apps/main/services/note-service.js +6 -6
  23. package/sample/crud/apps/main/services/redis-demo-service.js +3 -3
  24. package/sample/crud/apps/main/services/user-service.js +62 -21
  25. package/sample/crud/apps/main/views/auth/login.ejs +6 -6
  26. package/sample/crud/apps/main/views/auth/register.ejs +46 -5
  27. package/sample/crud/apps/main/views/users/edit.ejs +42 -5
  28. package/sample/crud/apps/main/views/users/list.ejs +6 -2
  29. package/sample/crud/apps/main/views/users/new.ejs +56 -4
  30. package/sample/crud/docs/log_partition_design.mm.md +23 -0
  31. package/sample/crud/mega.config.js +63 -3
  32. package/sample/crud/package.json +3 -3
  33. package/sample/crud/scripts/start-ws-hub.sh +2 -2
  34. package/sample/simple/package.json +2 -2
  35. package/src/adapters/adapter-manager.js +2 -1
  36. package/src/adapters/adapter-options.js +30 -0
  37. package/src/adapters/maria-adapter.js +26 -3
  38. package/src/adapters/mega-db-adapter.js +7 -1
  39. package/src/adapters/mongo-adapter.js +19 -1
  40. package/src/adapters/postgres-adapter.js +25 -2
  41. package/src/adapters/sqlite-adapter.js +20 -1
  42. package/src/cli/commands/new.js +13 -3
  43. package/src/cli/commands/scaffold.js +137 -33
  44. package/src/cli/generators/index.js +82 -2
  45. package/src/cli/index.js +478 -104
  46. package/src/core/ajv-mapper.js +27 -2
  47. package/src/core/boot.js +485 -237
  48. package/src/core/cluster-metrics.js +13 -4
  49. package/src/core/config-validator.js +25 -0
  50. package/src/core/ctx-builder.js +6 -2
  51. package/src/core/envelope.js +112 -12
  52. package/src/core/hub-link.js +65 -4
  53. package/src/core/i18n.js +11 -1
  54. package/src/core/index.js +6 -2
  55. package/src/core/mega-app.js +223 -481
  56. package/src/core/mega-cluster.js +54 -13
  57. package/src/core/mega-server.js +40 -9
  58. package/src/core/migration/dialect-registry.js +107 -0
  59. package/src/core/migration/dialects/README.md +62 -0
  60. package/src/core/migration/dialects/maria.js +496 -0
  61. package/src/core/migration/dialects/mongo.js +824 -0
  62. package/src/core/migration/dialects/postgres.js +563 -0
  63. package/src/core/migration/dialects/sqlite.js +476 -0
  64. package/src/core/migration/differ.js +456 -0
  65. package/src/core/migration/generate.js +508 -0
  66. package/src/core/migration/journal.js +167 -0
  67. package/src/core/migration/model-scan.js +84 -0
  68. package/src/core/migration/mongo-migration-db.js +97 -0
  69. package/src/core/migration/schema-builder.js +400 -0
  70. package/src/core/migration/schema-validator.js +315 -0
  71. package/src/core/migration-lock.js +205 -0
  72. package/src/core/migration-runner.js +166 -38
  73. package/src/core/multipart.js +28 -5
  74. package/src/core/pipeline.js +129 -0
  75. package/src/core/router.js +70 -65
  76. package/src/core/scope-registry.js +0 -1
  77. package/src/core/security.js +67 -9
  78. package/src/core/workers-manager.js +12 -1
  79. package/src/core/ws-cluster.js +10 -3
  80. package/src/core/ws-message.js +48 -4
  81. package/src/core/ws-presence.js +624 -0
  82. package/src/core/ws-roster.js +4 -1
  83. package/src/core/ws-upgrade.js +118 -12
  84. package/src/index.js +1 -1
  85. package/src/lib/hub-protocol.js +29 -0
  86. package/src/lib/mega-health.js +25 -4
  87. package/src/lib/mega-job-queue.js +98 -21
  88. package/src/lib/mega-job.js +29 -0
  89. package/src/lib/mega-logger.js +1 -1
  90. package/src/lib/mega-metrics.js +3 -12
  91. package/src/lib/mega-plugin.js +34 -3
  92. package/src/lib/mega-schedule.js +40 -22
  93. package/src/lib/mega-shutdown.js +162 -49
  94. package/src/lib/mega-tracing.js +66 -19
  95. package/src/lib/mega-worker.js +5 -1
  96. package/src/lib/otel-resource.js +36 -0
  97. package/src/{cli → lib}/ws-hub.js +51 -8
  98. package/src/models/crud-sql-builder.js +133 -0
  99. package/src/models/mega-model.js +82 -2
  100. package/src/models/model-crud.js +483 -0
  101. package/src/models/mongo-crud.js +285 -0
  102. package/templates/model/code-mongo.tpl +35 -0
  103. package/templates/model/code.tpl +15 -1
  104. package/templates/model/test-mongo.tpl +38 -0
  105. package/templates/model/test.tpl +4 -0
  106. package/types/adapters/adapter-manager.d.ts +95 -0
  107. package/types/adapters/adapter-options.d.ts +91 -0
  108. package/types/adapters/file-adapter.d.ts +94 -0
  109. package/types/adapters/file-session-adapter.d.ts +101 -0
  110. package/types/adapters/index.d.ts +20 -0
  111. package/types/adapters/maria-adapter.d.ts +115 -0
  112. package/types/adapters/mega-adapter.d.ts +215 -0
  113. package/types/adapters/mega-bus-adapter.d.ts +45 -0
  114. package/types/adapters/mega-cache-adapter.d.ts +47 -0
  115. package/types/adapters/mega-db-adapter.d.ts +47 -0
  116. package/types/adapters/mega-lock-adapter.d.ts +62 -0
  117. package/types/adapters/mega-log-sink-adapter.d.ts +15 -0
  118. package/types/adapters/mega-session-adapter.d.ts +32 -0
  119. package/types/adapters/mongo-adapter.d.ts +139 -0
  120. package/types/adapters/nats-adapter.d.ts +108 -0
  121. package/types/adapters/postgres-adapter.d.ts +139 -0
  122. package/types/adapters/redis-adapter.d.ts +70 -0
  123. package/types/adapters/redis-session-adapter.d.ts +82 -0
  124. package/types/adapters/redlock-adapter.d.ts +149 -0
  125. package/types/adapters/registry.d.ts +46 -0
  126. package/types/adapters/sqlite-adapter.d.ts +106 -0
  127. package/types/auth/index.d.ts +24 -0
  128. package/types/cli/commands/console-cmd.d.ts +37 -0
  129. package/types/cli/commands/new.d.ts +16 -0
  130. package/types/cli/commands/routes.d.ts +36 -0
  131. package/types/cli/commands/scaffold.d.ts +78 -0
  132. package/types/cli/commands/test-cmd.d.ts +14 -0
  133. package/types/cli/generators/index.d.ts +112 -0
  134. package/types/cli/index.d.ts +249 -0
  135. package/types/cli/template-engine.d.ts +40 -0
  136. package/types/core/ajv-mapper.d.ts +27 -0
  137. package/types/core/boot.d.ts +233 -0
  138. package/types/core/cluster-metrics.d.ts +52 -0
  139. package/types/core/config-loader.d.ts +13 -0
  140. package/types/core/config-validator.d.ts +30 -0
  141. package/types/core/ctx-builder.d.ts +80 -0
  142. package/types/core/envelope.d.ts +79 -0
  143. package/types/core/error-mapper.d.ts +17 -0
  144. package/types/core/formbody.d.ts +41 -0
  145. package/types/core/hub-link.d.ts +264 -0
  146. package/types/core/i18n.d.ts +178 -0
  147. package/types/core/index.d.ts +28 -0
  148. package/types/core/mega-app.d.ts +529 -0
  149. package/types/core/mega-cluster.d.ts +104 -0
  150. package/types/core/mega-server.d.ts +91 -0
  151. package/types/core/mega-service.d.ts +31 -0
  152. package/types/core/migration/dialect-registry.d.ts +22 -0
  153. package/types/core/migration/dialects/maria.d.ts +99 -0
  154. package/types/core/migration/dialects/mongo.d.ts +89 -0
  155. package/types/core/migration/dialects/postgres.d.ts +117 -0
  156. package/types/core/migration/dialects/sqlite.d.ts +111 -0
  157. package/types/core/migration/differ.d.ts +47 -0
  158. package/types/core/migration/generate.d.ts +56 -0
  159. package/types/core/migration/journal.d.ts +52 -0
  160. package/types/core/migration/model-scan.d.ts +19 -0
  161. package/types/core/migration/mongo-migration-db.d.ts +7 -0
  162. package/types/core/migration/schema-builder.d.ts +197 -0
  163. package/types/core/migration/schema-validator.d.ts +20 -0
  164. package/types/core/migration-lock.d.ts +33 -0
  165. package/types/core/migration-runner.d.ts +101 -0
  166. package/types/core/multipart.d.ts +86 -0
  167. package/types/core/openapi.d.ts +62 -0
  168. package/types/core/pipeline.d.ts +92 -0
  169. package/types/core/router.d.ts +159 -0
  170. package/types/core/routes-loader.d.ts +21 -0
  171. package/types/core/scope-registry.d.ts +14 -0
  172. package/types/core/security.d.ts +77 -0
  173. package/types/core/services-loader.d.ts +27 -0
  174. package/types/core/session-cleanup-schedule.d.ts +19 -0
  175. package/types/core/session-store.d.ts +18 -0
  176. package/types/core/session.d.ts +77 -0
  177. package/types/core/static-assets.d.ts +73 -0
  178. package/types/core/template.d.ts +106 -0
  179. package/types/core/workers-manager.d.ts +79 -0
  180. package/types/core/ws-cluster.d.ts +208 -0
  181. package/types/core/ws-compression.d.ts +112 -0
  182. package/types/core/ws-controller.d.ts +65 -0
  183. package/types/core/ws-message.d.ts +106 -0
  184. package/types/core/ws-presence.d.ts +273 -0
  185. package/types/core/ws-roster.d.ts +96 -0
  186. package/types/core/ws-upgrade.d.ts +231 -0
  187. package/types/errors/config-error.d.ts +10 -0
  188. package/types/errors/http-errors.d.ts +120 -0
  189. package/types/errors/index.d.ts +3 -0
  190. package/types/errors/mega-error.d.ts +32 -0
  191. package/types/index.d.ts +39 -0
  192. package/types/lib/asp/config.d.ts +49 -0
  193. package/types/lib/asp/crypto.d.ts +43 -0
  194. package/types/lib/asp/errors.d.ts +30 -0
  195. package/types/lib/asp/nonce-cache.d.ts +52 -0
  196. package/types/lib/asp/plugin.d.ts +30 -0
  197. package/types/lib/asp/ws-terminator.d.ts +45 -0
  198. package/types/lib/env-mapper.d.ts +14 -0
  199. package/types/lib/hub-protocol.d.ts +106 -0
  200. package/types/lib/index.d.ts +22 -0
  201. package/types/lib/logger/telegram-core.d.ts +104 -0
  202. package/types/lib/logger/telegram-transport.d.ts +45 -0
  203. package/types/lib/mega-brute-force.d.ts +66 -0
  204. package/types/lib/mega-circuit-breaker.d.ts +241 -0
  205. package/types/lib/mega-cron.d.ts +66 -0
  206. package/types/lib/mega-hash.d.ts +32 -0
  207. package/types/lib/mega-health.d.ts +41 -0
  208. package/types/lib/mega-job-queue.d.ts +176 -0
  209. package/types/lib/mega-job-worker.d.ts +130 -0
  210. package/types/lib/mega-job.d.ts +138 -0
  211. package/types/lib/mega-logger.d.ts +45 -0
  212. package/types/lib/mega-metrics.d.ts +285 -0
  213. package/types/lib/mega-plugin.d.ts +245 -0
  214. package/types/lib/mega-retry.d.ts +85 -0
  215. package/types/lib/mega-schedule.d.ts +260 -0
  216. package/types/lib/mega-shutdown.d.ts +135 -0
  217. package/types/lib/mega-tracing.d.ts +224 -0
  218. package/types/lib/mega-worker.d.ts +127 -0
  219. package/types/lib/otel-resource.d.ts +16 -0
  220. package/types/lib/worker-runner/process-entry.d.ts +1 -0
  221. package/types/lib/worker-runner/task-dispatch.d.ts +28 -0
  222. package/types/lib/worker-runner/thread-entry.d.ts +1 -0
  223. package/types/lib/ws-hub.d.ts +234 -0
  224. package/types/models/crud-sql-builder.d.ts +48 -0
  225. package/types/models/index.d.ts +1 -0
  226. package/types/models/mega-model.d.ts +138 -0
  227. package/types/models/model-crud.d.ts +82 -0
  228. package/types/models/mongo-crud.d.ts +59 -0
  229. package/types/test/index.d.ts +84 -0
  230. package/.env +0 -127
  231. package/sample/crud/apps/main/migrations/20260606000002-add-auth-to-users.js +0 -30
  232. package/sample/crud/apps/main/models/note.js +0 -71
  233. package/sample/crud/apps/main/models/user.js +0 -86
  234. package/sample/crud/package-lock.json +0 -5665
  235. package/sample/crud/yarn.lock +0 -2142
  236. package/sample/simple/package-lock.json +0 -1851
@@ -0,0 +1,261 @@
1
+ {
2
+ "version": 1,
3
+ "generatedAt": "2026-06-12T09:25:43.484Z",
4
+ "adapters": {
5
+ "primary": {
6
+ "driver": "postgres",
7
+ "models": [
8
+ {
9
+ "name": "UserLevelModel",
10
+ "table": "user_levels",
11
+ "record": {
12
+ "table": "user_levels",
13
+ "adapter": "primary",
14
+ "columns": {
15
+ "id": {
16
+ "type": "serial",
17
+ "primary": true
18
+ },
19
+ "code": {
20
+ "type": "text",
21
+ "notNull": true,
22
+ "unique": true,
23
+ "comment": "회원 등급 코드"
24
+ },
25
+ "type_code": {
26
+ "type": "text",
27
+ "notNull": true,
28
+ "references": {
29
+ "model": "UserTypeModel",
30
+ "column": "code",
31
+ "onDelete": "cascade",
32
+ "table": "user_types"
33
+ },
34
+ "comment": "회원 유형 코드"
35
+ },
36
+ "sort_order": {
37
+ "type": "integer",
38
+ "notNull": true,
39
+ "default": 0,
40
+ "comment": "정렬 순서"
41
+ },
42
+ "name": {
43
+ "type": "text",
44
+ "notNull": true,
45
+ "comment": "회원 등급명"
46
+ },
47
+ "created_at": {
48
+ "type": "timestamptz",
49
+ "notNull": true,
50
+ "default": {
51
+ "raw": "now()"
52
+ },
53
+ "comment": "생성 시간"
54
+ }
55
+ },
56
+ "indexes": [
57
+ {
58
+ "columns": [
59
+ "type_code"
60
+ ],
61
+ "name": "idx_user_levels_type_code"
62
+ }
63
+ ]
64
+ }
65
+ },
66
+ {
67
+ "name": "UserModel",
68
+ "table": "users",
69
+ "record": {
70
+ "table": "users",
71
+ "adapter": "primary",
72
+ "columns": {
73
+ "id": {
74
+ "type": "bigSerial",
75
+ "primary": true
76
+ },
77
+ "uuid": {
78
+ "type": "uuid",
79
+ "notNull": true,
80
+ "default": {
81
+ "raw": "gen_random_uuid()"
82
+ },
83
+ "comment": "고유값"
84
+ },
85
+ "username": {
86
+ "type": "text",
87
+ "notNull": true,
88
+ "unique": true,
89
+ "comment": "로그인 아이디"
90
+ },
91
+ "password_hash": {
92
+ "type": "text",
93
+ "notNull": true,
94
+ "comment": "비밀번호 해시"
95
+ },
96
+ "type_code": {
97
+ "type": "text",
98
+ "notNull": true,
99
+ "default": "USER",
100
+ "comment": "회원 유형"
101
+ },
102
+ "level_code": {
103
+ "type": "text",
104
+ "notNull": true,
105
+ "default": "lv1",
106
+ "comment": "회원 등급"
107
+ },
108
+ "name": {
109
+ "type": "text",
110
+ "notNull": true,
111
+ "comment": "실명"
112
+ },
113
+ "nickname": {
114
+ "type": "text",
115
+ "notNull": true,
116
+ "unique": true,
117
+ "comment": "별명"
118
+ },
119
+ "email": {
120
+ "type": "text",
121
+ "comment": "이메일"
122
+ },
123
+ "phone_number": {
124
+ "type": "text",
125
+ "comment": "전화번호"
126
+ },
127
+ "last_login_at": {
128
+ "type": "timestamptz",
129
+ "comment": "마지막 로그인 시간"
130
+ },
131
+ "created_at": {
132
+ "type": "timestamptz",
133
+ "notNull": true,
134
+ "default": {
135
+ "raw": "now()"
136
+ },
137
+ "comment": "생성 시간"
138
+ }
139
+ },
140
+ "indexes": [
141
+ {
142
+ "columns": [
143
+ "uuid"
144
+ ],
145
+ "name": "idx_users_uuid",
146
+ "unique": true
147
+ },
148
+ {
149
+ "columns": [
150
+ "email"
151
+ ],
152
+ "name": "idx_users_email"
153
+ },
154
+ {
155
+ "columns": [
156
+ "type_code"
157
+ ],
158
+ "name": "idx_users_type_code"
159
+ },
160
+ {
161
+ "columns": [
162
+ "level_code"
163
+ ],
164
+ "name": "idx_users_level_code"
165
+ }
166
+ ]
167
+ }
168
+ },
169
+ {
170
+ "name": "UserTypeModel",
171
+ "table": "user_types",
172
+ "record": {
173
+ "table": "user_types",
174
+ "adapter": "primary",
175
+ "columns": {
176
+ "id": {
177
+ "type": "serial",
178
+ "primary": true
179
+ },
180
+ "code": {
181
+ "type": "text",
182
+ "notNull": true,
183
+ "unique": true,
184
+ "comment": "회원 유형 코드"
185
+ },
186
+ "sort_order": {
187
+ "type": "integer",
188
+ "notNull": true,
189
+ "default": 0,
190
+ "comment": "정렬 순서"
191
+ },
192
+ "name": {
193
+ "type": "text",
194
+ "notNull": true,
195
+ "comment": "회원 유형명"
196
+ },
197
+ "created_at": {
198
+ "type": "timestamptz",
199
+ "notNull": true,
200
+ "default": {
201
+ "raw": "now()"
202
+ },
203
+ "comment": "생성 시간"
204
+ }
205
+ },
206
+ "indexes": []
207
+ }
208
+ },
209
+ {
210
+ "name": "WalletModel",
211
+ "table": "wallets",
212
+ "record": {
213
+ "table": "wallets",
214
+ "adapter": "primary",
215
+ "columns": {
216
+ "id": {
217
+ "type": "serial",
218
+ "primary": true
219
+ },
220
+ "user_id": {
221
+ "type": "bigInteger",
222
+ "notNull": true,
223
+ "references": {
224
+ "model": "UserModel",
225
+ "column": "id",
226
+ "onDelete": "cascade",
227
+ "table": "users"
228
+ },
229
+ "comment": "회원 ID"
230
+ },
231
+ "balance": {
232
+ "type": "decimal",
233
+ "precision": 20,
234
+ "scale": 4,
235
+ "notNull": true,
236
+ "default": 0,
237
+ "comment": "잔액"
238
+ },
239
+ "created_at": {
240
+ "type": "timestamptz",
241
+ "notNull": true,
242
+ "default": {
243
+ "raw": "now()"
244
+ },
245
+ "comment": "생성 시간"
246
+ }
247
+ },
248
+ "indexes": [
249
+ {
250
+ "columns": [
251
+ "user_id"
252
+ ],
253
+ "name": "idx_wallets_user_id"
254
+ }
255
+ ]
256
+ }
257
+ }
258
+ ]
259
+ }
260
+ }
261
+ }
@@ -0,0 +1,261 @@
1
+ {
2
+ "version": 1,
3
+ "generatedAt": "2026-06-12T09:25:43.485Z",
4
+ "adapters": {
5
+ "primary": {
6
+ "driver": "postgres",
7
+ "models": [
8
+ {
9
+ "name": "UserLevelModel",
10
+ "table": "user_levels",
11
+ "record": {
12
+ "table": "user_levels",
13
+ "adapter": "primary",
14
+ "columns": {
15
+ "id": {
16
+ "type": "serial",
17
+ "primary": true
18
+ },
19
+ "code": {
20
+ "type": "text",
21
+ "notNull": true,
22
+ "unique": true,
23
+ "comment": "회원 등급 코드"
24
+ },
25
+ "type_code": {
26
+ "type": "text",
27
+ "notNull": true,
28
+ "references": {
29
+ "model": "UserTypeModel",
30
+ "column": "code",
31
+ "onDelete": "cascade",
32
+ "table": "user_types"
33
+ },
34
+ "comment": "회원 유형 코드"
35
+ },
36
+ "sort_order": {
37
+ "type": "integer",
38
+ "notNull": true,
39
+ "default": 0,
40
+ "comment": "정렬 순서"
41
+ },
42
+ "name": {
43
+ "type": "text",
44
+ "notNull": true,
45
+ "comment": "회원 등급명"
46
+ },
47
+ "created_at": {
48
+ "type": "timestamptz",
49
+ "notNull": true,
50
+ "default": {
51
+ "raw": "now()"
52
+ },
53
+ "comment": "생성 시간"
54
+ }
55
+ },
56
+ "indexes": [
57
+ {
58
+ "columns": [
59
+ "type_code"
60
+ ],
61
+ "name": "idx_user_levels_type_code"
62
+ }
63
+ ]
64
+ }
65
+ },
66
+ {
67
+ "name": "UserModel",
68
+ "table": "users",
69
+ "record": {
70
+ "table": "users",
71
+ "adapter": "primary",
72
+ "columns": {
73
+ "id": {
74
+ "type": "bigSerial",
75
+ "primary": true
76
+ },
77
+ "uuid": {
78
+ "type": "uuid",
79
+ "notNull": true,
80
+ "default": {
81
+ "raw": "gen_random_uuid()"
82
+ },
83
+ "comment": "고유값"
84
+ },
85
+ "username": {
86
+ "type": "text",
87
+ "notNull": true,
88
+ "unique": true,
89
+ "comment": "로그인 아이디"
90
+ },
91
+ "password_hash": {
92
+ "type": "text",
93
+ "notNull": true,
94
+ "comment": "비밀번호 해시"
95
+ },
96
+ "type_code": {
97
+ "type": "text",
98
+ "notNull": true,
99
+ "default": "USER",
100
+ "comment": "회원 유형"
101
+ },
102
+ "level_code": {
103
+ "type": "text",
104
+ "notNull": true,
105
+ "default": "lv1",
106
+ "comment": "회원 등급"
107
+ },
108
+ "name": {
109
+ "type": "text",
110
+ "notNull": true,
111
+ "comment": "실명"
112
+ },
113
+ "nickname": {
114
+ "type": "text",
115
+ "notNull": true,
116
+ "unique": true,
117
+ "comment": "별명"
118
+ },
119
+ "email": {
120
+ "type": "text",
121
+ "comment": "이메일"
122
+ },
123
+ "phone_number": {
124
+ "type": "text",
125
+ "comment": "전화번호"
126
+ },
127
+ "last_login_at": {
128
+ "type": "timestamptz",
129
+ "comment": "마지막 로그인 시간"
130
+ },
131
+ "created_at": {
132
+ "type": "timestamptz",
133
+ "notNull": true,
134
+ "default": {
135
+ "raw": "now()"
136
+ },
137
+ "comment": "생성 시간"
138
+ }
139
+ },
140
+ "indexes": [
141
+ {
142
+ "columns": [
143
+ "uuid"
144
+ ],
145
+ "name": "idx_users_uuid",
146
+ "unique": true
147
+ },
148
+ {
149
+ "columns": [
150
+ "email"
151
+ ],
152
+ "name": "idx_users_email"
153
+ },
154
+ {
155
+ "columns": [
156
+ "type_code"
157
+ ],
158
+ "name": "idx_users_type_code"
159
+ },
160
+ {
161
+ "columns": [
162
+ "level_code"
163
+ ],
164
+ "name": "idx_users_level_code"
165
+ }
166
+ ]
167
+ }
168
+ },
169
+ {
170
+ "name": "UserTypeModel",
171
+ "table": "user_types",
172
+ "record": {
173
+ "table": "user_types",
174
+ "adapter": "primary",
175
+ "columns": {
176
+ "id": {
177
+ "type": "serial",
178
+ "primary": true
179
+ },
180
+ "code": {
181
+ "type": "text",
182
+ "notNull": true,
183
+ "unique": true,
184
+ "comment": "회원 유형 코드"
185
+ },
186
+ "sort_order": {
187
+ "type": "integer",
188
+ "notNull": true,
189
+ "default": 0,
190
+ "comment": "정렬 순서"
191
+ },
192
+ "name": {
193
+ "type": "text",
194
+ "notNull": true,
195
+ "comment": "회원 유형명"
196
+ },
197
+ "created_at": {
198
+ "type": "timestamptz",
199
+ "notNull": true,
200
+ "default": {
201
+ "raw": "now()"
202
+ },
203
+ "comment": "생성 시간"
204
+ }
205
+ },
206
+ "indexes": []
207
+ }
208
+ },
209
+ {
210
+ "name": "WalletModel",
211
+ "table": "wallets",
212
+ "record": {
213
+ "table": "wallets",
214
+ "adapter": "primary",
215
+ "columns": {
216
+ "id": {
217
+ "type": "serial",
218
+ "primary": true
219
+ },
220
+ "user_id": {
221
+ "type": "bigInteger",
222
+ "notNull": true,
223
+ "references": {
224
+ "model": "UserModel",
225
+ "column": "id",
226
+ "onDelete": "cascade",
227
+ "table": "users"
228
+ },
229
+ "comment": "회원 ID"
230
+ },
231
+ "balance": {
232
+ "type": "decimal",
233
+ "precision": 20,
234
+ "scale": 4,
235
+ "notNull": true,
236
+ "default": 0,
237
+ "comment": "잔액"
238
+ },
239
+ "created_at": {
240
+ "type": "timestamptz",
241
+ "notNull": true,
242
+ "default": {
243
+ "raw": "now()"
244
+ },
245
+ "comment": "생성 시간"
246
+ }
247
+ },
248
+ "indexes": [
249
+ {
250
+ "columns": [
251
+ "user_id"
252
+ ],
253
+ "name": "idx_wallets_user_id"
254
+ }
255
+ ]
256
+ }
257
+ }
258
+ ]
259
+ }
260
+ }
261
+ }
@@ -45,22 +45,22 @@ export class AuthController {
45
45
 
46
46
  /** POST /auth/login — 인증 → 세션 생성 → 관리 UI. 잠김/실패 시 폼 재렌더. @param {any} req @param {any} reply @param {any} ctx */
47
47
  static async login(req, reply, ctx) {
48
- const email = typeof req.body?.email === 'string' ? req.body.email.trim().toLowerCase() : ''
48
+ const username = typeof req.body?.username === 'string' ? req.body.username.trim() : ''
49
49
  const password = typeof req.body?.password === 'string' ? req.body.password : ''
50
- // subject = IP:emailemail 단독 잠금은 피해자 계정을 공격자가 잠그는 DoS 가 가능(I-1).
51
- const subject = `${req.ip}:${email}`
50
+ // subject = IP:usernameusername 단독 잠금은 피해자 계정을 공격자가 잠그는 DoS 가 가능(I-1).
51
+ const subject = `${req.ip}:${username}`
52
52
  const bf = ctx.bruteForce
53
53
 
54
54
  const status = await bf.check(subject)
55
55
  if (status.isLocked) {
56
- return AuthController.#renderLogin(reply, ctx, { email }, ctx.t('login_locked', '로그인 시도가 너무 많습니다. 잠시 후 다시 시도하세요.'), 423)
56
+ return AuthController.#renderLogin(reply, ctx, { username }, ctx.t('login_locked', '로그인 시도가 너무 많습니다. 잠시 후 다시 시도하세요.'), 423)
57
57
  }
58
58
 
59
- const user = await ctx.services.auth.authenticate({ email, password })
59
+ const user = await ctx.services.auth.authenticate({ username, password })
60
60
  if (!user) {
61
61
  const after = await bf.fail(subject)
62
- const msg = after.isLocked ? ctx.t('login_locked', '로그인 시도가 너무 많습니다. 잠시 후 다시 시도하세요.') : ctx.t('login_failed', '이메일 또는 비밀번호가 올바르지 않습니다.')
63
- return AuthController.#renderLogin(reply, ctx, { email }, msg, after.isLocked ? 423 : 401)
62
+ const msg = after.isLocked ? ctx.t('login_locked', '로그인 시도가 너무 많습니다. 잠시 후 다시 시도하세요.') : ctx.t('login_failed', '아이디 또는 비밀번호가 올바르지 않습니다.')
63
+ return AuthController.#renderLogin(reply, ctx, { username }, msg, after.isLocked ? 423 : 401)
64
64
  }
65
65
 
66
66
  await bf.reset(subject)
@@ -99,7 +99,13 @@ export class AuthController {
99
99
  reply.code(err instanceof MegaConflictError ? 409 : 400)
100
100
  return ctx.render('auth/register', {
101
101
  title: ctx.t('register_title', '회원가입'),
102
- values: { name: req.body?.name ?? '', email: req.body?.email ?? '' },
102
+ values: {
103
+ username: req.body?.username ?? '',
104
+ name: req.body?.name ?? '',
105
+ nickname: req.body?.nickname ?? '',
106
+ email: req.body?.email ?? '',
107
+ phone_number: req.body?.phone_number ?? '',
108
+ },
103
109
  invalid: AuthController.#invalidFields(err),
104
110
  error: err.message,
105
111
  currentUser: null,
@@ -112,7 +118,7 @@ export class AuthController {
112
118
 
113
119
  /**
114
120
  * 로그인 폼 재렌더 헬퍼(상태코드 + 에러 메시지 + 입력 보존 + 새 CSRF 토큰).
115
- * @param {any} reply @param {any} ctx @param {{ email: string }} values @param {string} error @param {number} code
121
+ * @param {any} reply @param {any} ctx @param {{ username: string }} values @param {string} error @param {number} code
116
122
  */
117
123
  static #renderLogin(reply, ctx, values, error, code) {
118
124
  reply.code(code)
@@ -128,17 +134,19 @@ export class AuthController {
128
134
 
129
135
  /**
130
136
  * 회원가입 검증/충돌 에러 → 폼 is-invalid 표시용 필드 맵.
131
- * @param {unknown} err @returns {{ name: boolean, email: boolean, password: boolean }}
137
+ * @param {unknown} err @returns {{ username: boolean, name: boolean, nickname: boolean, password: boolean }}
132
138
  */
133
139
  static #invalidFields(err) {
134
140
  if (err instanceof MegaValidationError) {
135
141
  const d = /** @type {any} */ (err).details ?? {}
136
- // auth-service 는 details.{name,email,password} = "값이 유효한가"(true=정상) 로 준다 → 없으면 invalid.
137
- return { name: !d.name, email: !d.email, password: !d.password }
142
+ // auth-service 는 details.{username,name,nickname,password} = "값이 유효한가"(true=정상) → 없으면 invalid.
143
+ return { username: !d.username, name: !d.name, nickname: !d.nickname, password: !d.password }
138
144
  }
139
145
  if (err instanceof MegaConflictError) {
140
- return { name: false, email: true, password: false } // 이메일 중복.
146
+ // username/nickname 중복 code 구분(user.nickname_taken vs user.username_taken).
147
+ const isNick = /** @type {any} */ (err).code === 'user.nickname_taken'
148
+ return { username: !isNick, name: false, nickname: isNick, password: false }
141
149
  }
142
- return { name: false, email: false, password: false }
150
+ return { username: false, name: false, nickname: false, password: false }
143
151
  }
144
152
  }
@@ -19,18 +19,20 @@ const NOTICE_KEYS = new Set(['created', 'updated', 'deleted'])
19
19
  /**
20
20
  * 검증/충돌 에러에서 어떤 필드가 잘못됐는지 계산한다(폼 is-invalid 표시용).
21
21
  * @param {unknown} err
22
- * @returns {{ name: boolean, email: boolean }}
22
+ * @returns {{ username: boolean, name: boolean, nickname: boolean, password: boolean }}
23
23
  */
24
24
  function invalidFields(err) {
25
25
  if (err instanceof MegaValidationError) {
26
26
  const d = /** @type {any} */ (err).details ?? {}
27
- // user-service 는 details.{name,email} = "값이 있는가"(true=정상) 로 준다 → 없으면 invalid.
28
- return { name: !d.name, email: !d.email }
27
+ // user-service 는 details.{username,name,nickname,password} = "값이 유효한가"(true=정상) → 없으면 invalid.
28
+ return { username: !d.username, name: !d.name, nickname: !d.nickname, password: !d.password }
29
29
  }
30
30
  if (err instanceof MegaConflictError) {
31
- return { name: false, email: true } // 이메일 중복.
31
+ // username/nickname 중복 code 구분.
32
+ const isNick = /** @type {any} */ (err).code === 'user.nickname_taken'
33
+ return { username: !isNick, name: false, nickname: isNick, password: false }
32
34
  }
33
- return { name: false, email: false }
35
+ return { username: false, name: false, nickname: false, password: false }
34
36
  }
35
37
 
36
38
  export class WebController {