@dewtech/dare-cli 3.0.0 → 3.1.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 (453) hide show
  1. package/README.md +45 -39
  2. package/dist/bin/dare.js +1 -3
  3. package/dist/bin/dare.js.map +1 -1
  4. package/dist/commands/__tests__/init.integration.spec.d.ts +2 -0
  5. package/dist/commands/__tests__/init.integration.spec.d.ts.map +1 -0
  6. package/dist/commands/__tests__/init.integration.spec.js +134 -0
  7. package/dist/commands/__tests__/init.integration.spec.js.map +1 -0
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +84 -1
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/new.d.ts.map +1 -1
  12. package/dist/commands/new.js +2 -1
  13. package/dist/commands/new.js.map +1 -1
  14. package/dist/mcp-server/bin/server.js +0 -0
  15. package/dist/stacks/__tests__/dna-emitter.spec.d.ts +2 -0
  16. package/dist/stacks/__tests__/dna-emitter.spec.d.ts.map +1 -0
  17. package/dist/stacks/__tests__/dna-emitter.spec.js +207 -0
  18. package/dist/stacks/__tests__/dna-emitter.spec.js.map +1 -0
  19. package/dist/stacks/__tests__/dna.spec.d.ts +2 -0
  20. package/dist/stacks/__tests__/dna.spec.d.ts.map +1 -0
  21. package/dist/stacks/__tests__/dna.spec.js +211 -0
  22. package/dist/stacks/__tests__/dna.spec.js.map +1 -0
  23. package/dist/stacks/__tests__/parity-rails.fixture.json +228 -0
  24. package/dist/stacks/__tests__/parity-rails.spec.d.ts +2 -0
  25. package/dist/stacks/__tests__/parity-rails.spec.d.ts.map +1 -0
  26. package/dist/stacks/__tests__/parity-rails.spec.js +99 -0
  27. package/dist/stacks/__tests__/parity-rails.spec.js.map +1 -0
  28. package/dist/stacks/__tests__/registry.spec.d.ts +2 -0
  29. package/dist/stacks/__tests__/registry.spec.d.ts.map +1 -0
  30. package/dist/stacks/__tests__/registry.spec.js +101 -0
  31. package/dist/stacks/__tests__/registry.spec.js.map +1 -0
  32. package/dist/stacks/__tests__/template-engine.spec.d.ts +2 -0
  33. package/dist/stacks/__tests__/template-engine.spec.d.ts.map +1 -0
  34. package/dist/stacks/__tests__/template-engine.spec.js +149 -0
  35. package/dist/stacks/__tests__/template-engine.spec.js.map +1 -0
  36. package/dist/stacks/dna-emitter.d.ts +45 -0
  37. package/dist/stacks/dna-emitter.d.ts.map +1 -0
  38. package/dist/stacks/dna-emitter.js +267 -0
  39. package/dist/stacks/dna-emitter.js.map +1 -0
  40. package/dist/stacks/go-gin/__tests__/scaffold.spec.d.ts +2 -0
  41. package/dist/stacks/go-gin/__tests__/scaffold.spec.d.ts.map +1 -0
  42. package/dist/stacks/go-gin/__tests__/scaffold.spec.js +221 -0
  43. package/dist/stacks/go-gin/__tests__/scaffold.spec.js.map +1 -0
  44. package/dist/stacks/go-gin/scaffold.d.ts +3 -0
  45. package/dist/stacks/go-gin/scaffold.d.ts.map +1 -0
  46. package/dist/stacks/go-gin/scaffold.js +105 -0
  47. package/dist/stacks/go-gin/scaffold.js.map +1 -0
  48. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.d.ts +2 -0
  49. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.d.ts.map +1 -0
  50. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.js +215 -0
  51. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.js.map +1 -0
  52. package/dist/stacks/go-stdlib/scaffold.d.ts +3 -0
  53. package/dist/stacks/go-stdlib/scaffold.d.ts.map +1 -0
  54. package/dist/stacks/go-stdlib/scaffold.js +106 -0
  55. package/dist/stacks/go-stdlib/scaffold.js.map +1 -0
  56. package/dist/stacks/mcp-go/__tests__/scaffold.spec.d.ts +2 -0
  57. package/dist/stacks/mcp-go/__tests__/scaffold.spec.d.ts.map +1 -0
  58. package/dist/stacks/mcp-go/__tests__/scaffold.spec.js +203 -0
  59. package/dist/stacks/mcp-go/__tests__/scaffold.spec.js.map +1 -0
  60. package/dist/stacks/mcp-go/scaffold.d.ts +3 -0
  61. package/dist/stacks/mcp-go/scaffold.d.ts.map +1 -0
  62. package/dist/stacks/mcp-go/scaffold.js +94 -0
  63. package/dist/stacks/mcp-go/scaffold.js.map +1 -0
  64. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.d.ts +2 -0
  65. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.d.ts.map +1 -0
  66. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.js +236 -0
  67. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.js.map +1 -0
  68. package/dist/stacks/mcp-node-ts/scaffold.d.ts +3 -0
  69. package/dist/stacks/mcp-node-ts/scaffold.d.ts.map +1 -0
  70. package/dist/stacks/mcp-node-ts/scaffold.js +95 -0
  71. package/dist/stacks/mcp-node-ts/scaffold.js.map +1 -0
  72. package/dist/stacks/mcp-python/__tests__/scaffold.spec.d.ts +2 -0
  73. package/dist/stacks/mcp-python/__tests__/scaffold.spec.d.ts.map +1 -0
  74. package/dist/stacks/mcp-python/__tests__/scaffold.spec.js +228 -0
  75. package/dist/stacks/mcp-python/__tests__/scaffold.spec.js.map +1 -0
  76. package/dist/stacks/mcp-python/scaffold.d.ts +3 -0
  77. package/dist/stacks/mcp-python/scaffold.d.ts.map +1 -0
  78. package/dist/stacks/mcp-python/scaffold.js +98 -0
  79. package/dist/stacks/mcp-python/scaffold.js.map +1 -0
  80. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.d.ts +2 -0
  81. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.d.ts.map +1 -0
  82. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.js +213 -0
  83. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.js.map +1 -0
  84. package/dist/stacks/mcp-rust/scaffold.d.ts +3 -0
  85. package/dist/stacks/mcp-rust/scaffold.d.ts.map +1 -0
  86. package/dist/stacks/mcp-rust/scaffold.js +98 -0
  87. package/dist/stacks/mcp-rust/scaffold.js.map +1 -0
  88. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.d.ts +2 -0
  89. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.d.ts.map +1 -0
  90. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.js +172 -0
  91. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.js.map +1 -0
  92. package/dist/stacks/node-nestjs/scaffold.d.ts +3 -0
  93. package/dist/stacks/node-nestjs/scaffold.d.ts.map +1 -0
  94. package/dist/stacks/node-nestjs/scaffold.js +117 -0
  95. package/dist/stacks/node-nestjs/scaffold.js.map +1 -0
  96. package/dist/stacks/php-laravel/__tests__/scaffold.spec.d.ts +2 -0
  97. package/dist/stacks/php-laravel/__tests__/scaffold.spec.d.ts.map +1 -0
  98. package/dist/stacks/php-laravel/__tests__/scaffold.spec.js +205 -0
  99. package/dist/stacks/php-laravel/__tests__/scaffold.spec.js.map +1 -0
  100. package/dist/stacks/php-laravel/scaffold.d.ts +3 -0
  101. package/dist/stacks/php-laravel/scaffold.d.ts.map +1 -0
  102. package/dist/stacks/php-laravel/scaffold.js +109 -0
  103. package/dist/stacks/php-laravel/scaffold.js.map +1 -0
  104. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.d.ts +2 -0
  105. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.d.ts.map +1 -0
  106. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.js +168 -0
  107. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.js.map +1 -0
  108. package/dist/stacks/python-fastapi/scaffold.d.ts +3 -0
  109. package/dist/stacks/python-fastapi/scaffold.d.ts.map +1 -0
  110. package/dist/stacks/python-fastapi/scaffold.js +108 -0
  111. package/dist/stacks/python-fastapi/scaffold.js.map +1 -0
  112. package/dist/stacks/registry.d.ts +38 -0
  113. package/dist/stacks/registry.d.ts.map +1 -0
  114. package/dist/stacks/registry.js +153 -0
  115. package/dist/stacks/registry.js.map +1 -0
  116. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.d.ts +6 -0
  117. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.d.ts.map +1 -0
  118. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.js +604 -0
  119. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.js.map +1 -0
  120. package/dist/stacks/ruby-rails-8/scaffold.d.ts +91 -0
  121. package/dist/stacks/ruby-rails-8/scaffold.d.ts.map +1 -0
  122. package/dist/stacks/ruby-rails-8/scaffold.js +410 -0
  123. package/dist/stacks/ruby-rails-8/scaffold.js.map +1 -0
  124. package/dist/stacks/rust-axum/__tests__/scaffold.spec.d.ts +2 -0
  125. package/dist/stacks/rust-axum/__tests__/scaffold.spec.d.ts.map +1 -0
  126. package/dist/stacks/rust-axum/__tests__/scaffold.spec.js +203 -0
  127. package/dist/stacks/rust-axum/__tests__/scaffold.spec.js.map +1 -0
  128. package/dist/stacks/rust-axum/scaffold.d.ts +3 -0
  129. package/dist/stacks/rust-axum/scaffold.d.ts.map +1 -0
  130. package/dist/stacks/rust-axum/scaffold.js +105 -0
  131. package/dist/stacks/rust-axum/scaffold.js.map +1 -0
  132. package/dist/stacks/template-engine.d.ts +38 -0
  133. package/dist/stacks/template-engine.d.ts.map +1 -0
  134. package/dist/stacks/template-engine.js +134 -0
  135. package/dist/stacks/template-engine.js.map +1 -0
  136. package/dist/stacks/types.d.ts +69 -0
  137. package/dist/stacks/types.d.ts.map +1 -0
  138. package/dist/stacks/types.js +29 -0
  139. package/dist/stacks/types.js.map +1 -0
  140. package/dist/utils/project-generator.d.ts +1 -1
  141. package/dist/utils/project-generator.d.ts.map +1 -1
  142. package/dist/utils/project-generator.js +16 -7
  143. package/dist/utils/project-generator.js.map +1 -1
  144. package/dist/utils/stack-bootstrap.d.ts +3 -2
  145. package/dist/utils/stack-bootstrap.d.ts.map +1 -1
  146. package/dist/utils/stack-bootstrap.js +46 -16
  147. package/dist/utils/stack-bootstrap.js.map +1 -1
  148. package/package.json +91 -87
  149. package/templates/stacks/go-gin/.dare/skills.yml +11 -0
  150. package/templates/stacks/go-gin/.env.example +24 -0
  151. package/templates/stacks/go-gin/.github/workflows/dare-ci.yml +42 -0
  152. package/templates/stacks/go-gin/README.md.tpl +38 -0
  153. package/templates/stacks/go-gin/cmd/server/main.go.tpl +78 -0
  154. package/templates/stacks/go-gin/db/migrations/0001_create_users.down.sql +2 -0
  155. package/templates/stacks/go-gin/db/migrations/0001_create_users.up.sql +12 -0
  156. package/templates/stacks/go-gin/db/queries/users.sql +23 -0
  157. package/templates/stacks/go-gin/gitignore +7 -0
  158. package/templates/stacks/go-gin/go.mod.tpl +17 -0
  159. package/templates/stacks/go-gin/internal/config/config.go +41 -0
  160. package/templates/stacks/go-gin/internal/db/postgres.go.tpl +25 -0
  161. package/templates/stacks/go-gin/internal/handler/auth_handler.go.tpl +72 -0
  162. package/templates/stacks/go-gin/internal/handler/users_handler.go.tpl +72 -0
  163. package/templates/stacks/go-gin/internal/handler/ws_handler.go +37 -0
  164. package/templates/stacks/go-gin/internal/llm/dummy.go +14 -0
  165. package/templates/stacks/go-gin/internal/llm/provider.go +8 -0
  166. package/templates/stacks/go-gin/internal/middleware/jwt.go.tpl +58 -0
  167. package/templates/stacks/go-gin/internal/middleware/rate_limit.go +55 -0
  168. package/templates/stacks/go-gin/internal/model/user.go +17 -0
  169. package/templates/stacks/go-gin/internal/repository/users_repository.go.tpl +79 -0
  170. package/templates/stacks/go-gin/internal/service/auth_service.go.tpl +55 -0
  171. package/templates/stacks/go-gin/internal/service/users_service.go.tpl +53 -0
  172. package/templates/stacks/go-gin/llms.txt.tpl +54 -0
  173. package/templates/stacks/go-gin/openapi.json.tpl +46 -0
  174. package/templates/stacks/go-gin/sqlc.yaml +14 -0
  175. package/templates/stacks/go-gin/tests/smoke_test.go.tpl +22 -0
  176. package/templates/stacks/go-stdlib/.dare/skills.yml +11 -0
  177. package/templates/stacks/go-stdlib/.env.example +24 -0
  178. package/templates/stacks/go-stdlib/.github/workflows/dare-ci.yml +42 -0
  179. package/templates/stacks/go-stdlib/README.md.tpl +41 -0
  180. package/templates/stacks/go-stdlib/cmd/server/main.go.tpl +82 -0
  181. package/templates/stacks/go-stdlib/db/migrations/0001_create_users.down.sql +2 -0
  182. package/templates/stacks/go-stdlib/db/migrations/0001_create_users.up.sql +12 -0
  183. package/templates/stacks/go-stdlib/db/queries/users.sql +23 -0
  184. package/templates/stacks/go-stdlib/gitignore +6 -0
  185. package/templates/stacks/go-stdlib/go.mod.tpl +15 -0
  186. package/templates/stacks/go-stdlib/internal/config/config.go +41 -0
  187. package/templates/stacks/go-stdlib/internal/db/postgres.go.tpl +24 -0
  188. package/templates/stacks/go-stdlib/internal/handler/auth_handler.go.tpl +71 -0
  189. package/templates/stacks/go-stdlib/internal/handler/users_handler.go.tpl +84 -0
  190. package/templates/stacks/go-stdlib/internal/handler/ws_handler.go +36 -0
  191. package/templates/stacks/go-stdlib/internal/httpx/json.go +32 -0
  192. package/templates/stacks/go-stdlib/internal/llm/dummy.go +14 -0
  193. package/templates/stacks/go-stdlib/internal/llm/provider.go +8 -0
  194. package/templates/stacks/go-stdlib/internal/middleware/chain.go +21 -0
  195. package/templates/stacks/go-stdlib/internal/middleware/cors.go +27 -0
  196. package/templates/stacks/go-stdlib/internal/middleware/jwt.go.tpl +51 -0
  197. package/templates/stacks/go-stdlib/internal/middleware/rate_limit.go +81 -0
  198. package/templates/stacks/go-stdlib/internal/model/user.go +17 -0
  199. package/templates/stacks/go-stdlib/internal/repository/users_repository.go.tpl +75 -0
  200. package/templates/stacks/go-stdlib/internal/service/auth_service.go.tpl +55 -0
  201. package/templates/stacks/go-stdlib/internal/service/users_service.go.tpl +53 -0
  202. package/templates/stacks/go-stdlib/llms.txt.tpl +60 -0
  203. package/templates/stacks/go-stdlib/openapi.json.tpl +46 -0
  204. package/templates/stacks/go-stdlib/sqlc.yaml +14 -0
  205. package/templates/stacks/go-stdlib/tests/smoke_test.go.tpl +45 -0
  206. package/templates/stacks/mcp-go/.dare/skills.yml +8 -0
  207. package/templates/stacks/mcp-go/.env.example +14 -0
  208. package/templates/stacks/mcp-go/.github/workflows/dare-ci.yml +42 -0
  209. package/templates/stacks/mcp-go/README.md.tpl +50 -0
  210. package/templates/stacks/mcp-go/cmd/server/main.go.tpl +62 -0
  211. package/templates/stacks/mcp-go/gitignore +6 -0
  212. package/templates/stacks/mcp-go/go.mod.tpl +9 -0
  213. package/templates/stacks/mcp-go/internal/prompts/summarize.go +9 -0
  214. package/templates/stacks/mcp-go/internal/server/server.go.tpl +80 -0
  215. package/templates/stacks/mcp-go/internal/tools/echo.go +15 -0
  216. package/templates/stacks/mcp-go/internal/transports/http.go.tpl +21 -0
  217. package/templates/stacks/mcp-go/internal/transports/sse.go.tpl +17 -0
  218. package/templates/stacks/mcp-go/internal/transports/stdio.go.tpl +14 -0
  219. package/templates/stacks/mcp-go/llms.txt.tpl +60 -0
  220. package/templates/stacks/mcp-go/openapi.json.tpl +31 -0
  221. package/templates/stacks/mcp-go/tests/echo_test.go.tpl +37 -0
  222. package/templates/stacks/mcp-node-ts/.dare/skills.yml +8 -0
  223. package/templates/stacks/mcp-node-ts/.env.example +16 -0
  224. package/templates/stacks/mcp-node-ts/.github/workflows/dare-ci.yml +54 -0
  225. package/templates/stacks/mcp-node-ts/README.md.hbs +49 -0
  226. package/templates/stacks/mcp-node-ts/gitignore +7 -0
  227. package/templates/stacks/mcp-node-ts/llms.txt.hbs +61 -0
  228. package/templates/stacks/mcp-node-ts/openapi.json.hbs +39 -0
  229. package/templates/stacks/mcp-node-ts/package.json.hbs +35 -0
  230. package/templates/stacks/mcp-node-ts/src/cli.ts.hbs +71 -0
  231. package/templates/stacks/mcp-node-ts/src/prompts/index.ts +36 -0
  232. package/templates/stacks/mcp-node-ts/src/server.ts.hbs +45 -0
  233. package/templates/stacks/mcp-node-ts/src/tools/echo.ts +23 -0
  234. package/templates/stacks/mcp-node-ts/src/tools/index.ts +18 -0
  235. package/templates/stacks/mcp-node-ts/src/transports/http.ts +68 -0
  236. package/templates/stacks/mcp-node-ts/src/transports/sse.ts +58 -0
  237. package/templates/stacks/mcp-node-ts/src/transports/stdio.ts +5 -0
  238. package/templates/stacks/mcp-node-ts/tests/echo.test.ts +50 -0
  239. package/templates/stacks/mcp-node-ts/tsconfig.json +17 -0
  240. package/templates/stacks/mcp-python/.dare/skills.yml +8 -0
  241. package/templates/stacks/mcp-python/.env.example +14 -0
  242. package/templates/stacks/mcp-python/.github/workflows/dare-ci.yml +42 -0
  243. package/templates/stacks/mcp-python/README.md.j2 +49 -0
  244. package/templates/stacks/mcp-python/gitignore +12 -0
  245. package/templates/stacks/mcp-python/llms.txt.j2 +56 -0
  246. package/templates/stacks/mcp-python/openapi.json.j2 +33 -0
  247. package/templates/stacks/mcp-python/pyproject.toml.j2 +37 -0
  248. package/templates/stacks/mcp-python/src/__init__.py +0 -0
  249. package/templates/stacks/mcp-python/src/cli.py.j2 +68 -0
  250. package/templates/stacks/mcp-python/src/prompts/__init__.py +0 -0
  251. package/templates/stacks/mcp-python/src/prompts/summarize.py +10 -0
  252. package/templates/stacks/mcp-python/src/server.py.j2 +28 -0
  253. package/templates/stacks/mcp-python/src/tools/__init__.py +0 -0
  254. package/templates/stacks/mcp-python/src/tools/echo.py +12 -0
  255. package/templates/stacks/mcp-python/src/transports/__init__.py +0 -0
  256. package/templates/stacks/mcp-python/src/transports/http.py +12 -0
  257. package/templates/stacks/mcp-python/src/transports/sse.py +13 -0
  258. package/templates/stacks/mcp-python/src/transports/stdio.py +6 -0
  259. package/templates/stacks/mcp-python/tests/__init__.py +0 -0
  260. package/templates/stacks/mcp-python/tests/test_echo.py +28 -0
  261. package/templates/stacks/mcp-rust/.dare/skills.yml +8 -0
  262. package/templates/stacks/mcp-rust/.env.example +14 -0
  263. package/templates/stacks/mcp-rust/.github/workflows/dare-ci.yml +38 -0
  264. package/templates/stacks/mcp-rust/Cargo.toml.tera +35 -0
  265. package/templates/stacks/mcp-rust/README.md.tera +50 -0
  266. package/templates/stacks/mcp-rust/gitignore +5 -0
  267. package/templates/stacks/mcp-rust/llms.txt.tera +60 -0
  268. package/templates/stacks/mcp-rust/openapi.json.tera +31 -0
  269. package/templates/stacks/mcp-rust/src/cli.rs.tera +33 -0
  270. package/templates/stacks/mcp-rust/src/lib.rs +6 -0
  271. package/templates/stacks/mcp-rust/src/main.rs.tera +30 -0
  272. package/templates/stacks/mcp-rust/src/prompts/mod.rs +1 -0
  273. package/templates/stacks/mcp-rust/src/prompts/summarize.rs +5 -0
  274. package/templates/stacks/mcp-rust/src/server.rs.tera +38 -0
  275. package/templates/stacks/mcp-rust/src/tools/echo.rs +18 -0
  276. package/templates/stacks/mcp-rust/src/tools/mod.rs +22 -0
  277. package/templates/stacks/mcp-rust/src/transports/http.rs +27 -0
  278. package/templates/stacks/mcp-rust/src/transports/mod.rs +3 -0
  279. package/templates/stacks/mcp-rust/src/transports/sse.rs +33 -0
  280. package/templates/stacks/mcp-rust/src/transports/stdio.rs +14 -0
  281. package/templates/stacks/mcp-rust/tests/echo_test.rs.tera +27 -0
  282. package/templates/stacks/node-nestjs/.dare/skills.yml +11 -0
  283. package/templates/stacks/node-nestjs/.env.example +21 -0
  284. package/templates/stacks/node-nestjs/.github/workflows/dare-ci.yml +54 -0
  285. package/templates/stacks/node-nestjs/README.md.hbs +35 -0
  286. package/templates/stacks/node-nestjs/gitignore +7 -0
  287. package/templates/stacks/node-nestjs/llms.txt.hbs +47 -0
  288. package/templates/stacks/node-nestjs/nest-cli.json +16 -0
  289. package/templates/stacks/node-nestjs/openapi.json.hbs +75 -0
  290. package/templates/stacks/node-nestjs/package.json.hbs +57 -0
  291. package/templates/stacks/node-nestjs/prisma/schema.prisma +25 -0
  292. package/templates/stacks/node-nestjs/prisma/seed.ts.hbs +25 -0
  293. package/templates/stacks/node-nestjs/src/app.module.ts +39 -0
  294. package/templates/stacks/node-nestjs/src/auth/auth.controller.ts +29 -0
  295. package/templates/stacks/node-nestjs/src/auth/auth.module.ts +25 -0
  296. package/templates/stacks/node-nestjs/src/auth/auth.service.ts +36 -0
  297. package/templates/stacks/node-nestjs/src/auth/dto/login-response.dto.ts +9 -0
  298. package/templates/stacks/node-nestjs/src/auth/dto/login.dto.ts +17 -0
  299. package/templates/stacks/node-nestjs/src/auth/jwt.strategy.ts +25 -0
  300. package/templates/stacks/node-nestjs/src/common/filters/problem-details.filter.ts +38 -0
  301. package/templates/stacks/node-nestjs/src/common/interceptors/json-response.interceptor.ts +13 -0
  302. package/templates/stacks/node-nestjs/src/main.ts.hbs +44 -0
  303. package/templates/stacks/node-nestjs/src/prisma/prisma.module.ts +9 -0
  304. package/templates/stacks/node-nestjs/src/prisma/prisma.service.ts +9 -0
  305. package/templates/stacks/node-nestjs/src/users/dto/create-user.dto.ts +22 -0
  306. package/templates/stacks/node-nestjs/src/users/dto/user.dto.ts +15 -0
  307. package/templates/stacks/node-nestjs/src/users/users.controller.ts +41 -0
  308. package/templates/stacks/node-nestjs/src/users/users.module.ts +11 -0
  309. package/templates/stacks/node-nestjs/src/users/users.repository.ts +38 -0
  310. package/templates/stacks/node-nestjs/src/users/users.service.ts +38 -0
  311. package/templates/stacks/node-nestjs/tsconfig.build.json +4 -0
  312. package/templates/stacks/node-nestjs/tsconfig.json +28 -0
  313. package/templates/stacks/php-laravel/.dare/skills.yml +11 -0
  314. package/templates/stacks/php-laravel/.env.example +41 -0
  315. package/templates/stacks/php-laravel/.github/workflows/dare-ci.yml +43 -0
  316. package/templates/stacks/php-laravel/README.md.hbs +36 -0
  317. package/templates/stacks/php-laravel/app/Http/Controllers/Api/AuthController.php +36 -0
  318. package/templates/stacks/php-laravel/app/Http/Controllers/Api/UsersController.php +33 -0
  319. package/templates/stacks/php-laravel/app/Http/Requests/CreateUserRequest.php +26 -0
  320. package/templates/stacks/php-laravel/app/Http/Requests/LoginRequest.php +34 -0
  321. package/templates/stacks/php-laravel/app/Llm/Contracts/LlmProvider.php +12 -0
  322. package/templates/stacks/php-laravel/app/Llm/Providers/DummyProvider.php +13 -0
  323. package/templates/stacks/php-laravel/app/Llm/Providers/OpenAiProvider.php +33 -0
  324. package/templates/stacks/php-laravel/app/Models/User.php +44 -0
  325. package/templates/stacks/php-laravel/app/Repositories/UsersRepository.php +32 -0
  326. package/templates/stacks/php-laravel/app/Services/AuthService.php +37 -0
  327. package/templates/stacks/php-laravel/app/Services/UsersService.php +57 -0
  328. package/templates/stacks/php-laravel/artisan +12 -0
  329. package/templates/stacks/php-laravel/bootstrap/app.php +29 -0
  330. package/templates/stacks/php-laravel/bootstrap/providers.php +5 -0
  331. package/templates/stacks/php-laravel/composer.json.hbs +58 -0
  332. package/templates/stacks/php-laravel/config/l5-swagger.php +41 -0
  333. package/templates/stacks/php-laravel/config/reverb.php +34 -0
  334. package/templates/stacks/php-laravel/config/sanctum.php +15 -0
  335. package/templates/stacks/php-laravel/database/migrations/2026_06_01_000001_create_users_table.php +27 -0
  336. package/templates/stacks/php-laravel/database/seeders/DatabaseSeeder.php +21 -0
  337. package/templates/stacks/php-laravel/gitignore +23 -0
  338. package/templates/stacks/php-laravel/llms.txt.hbs +53 -0
  339. package/templates/stacks/php-laravel/openapi.json.hbs +43 -0
  340. package/templates/stacks/php-laravel/phpstan.neon +9 -0
  341. package/templates/stacks/php-laravel/routes/api.php +13 -0
  342. package/templates/stacks/php-laravel/routes/channels.php +7 -0
  343. package/templates/stacks/php-laravel/tests/Feature/AuthTest.php +35 -0
  344. package/templates/stacks/php-laravel/tests/Feature/UsersTest.php +30 -0
  345. package/templates/stacks/php-laravel/tests/Pest.php +5 -0
  346. package/templates/stacks/python-fastapi/.dare/skills.yml +11 -0
  347. package/templates/stacks/python-fastapi/.env.example +21 -0
  348. package/templates/stacks/python-fastapi/.github/workflows/dare-ci.yml +43 -0
  349. package/templates/stacks/python-fastapi/README.md.j2 +35 -0
  350. package/templates/stacks/python-fastapi/alembic/env.py +46 -0
  351. package/templates/stacks/python-fastapi/alembic/script.py.mako +26 -0
  352. package/templates/stacks/python-fastapi/alembic/versions/0001_create_users.py.j2 +37 -0
  353. package/templates/stacks/python-fastapi/alembic.ini.j2 +39 -0
  354. package/templates/stacks/python-fastapi/app/__init__.py +0 -0
  355. package/templates/stacks/python-fastapi/app/core/__init__.py +0 -0
  356. package/templates/stacks/python-fastapi/app/core/config.py +24 -0
  357. package/templates/stacks/python-fastapi/app/core/security.py +34 -0
  358. package/templates/stacks/python-fastapi/app/db/__init__.py +0 -0
  359. package/templates/stacks/python-fastapi/app/db/session.py +22 -0
  360. package/templates/stacks/python-fastapi/app/main.py.j2 +36 -0
  361. package/templates/stacks/python-fastapi/app/models/__init__.py +3 -0
  362. package/templates/stacks/python-fastapi/app/models/user.py +30 -0
  363. package/templates/stacks/python-fastapi/app/repositories/__init__.py +0 -0
  364. package/templates/stacks/python-fastapi/app/repositories/user_repository.py +34 -0
  365. package/templates/stacks/python-fastapi/app/routers/__init__.py +0 -0
  366. package/templates/stacks/python-fastapi/app/routers/auth.py +37 -0
  367. package/templates/stacks/python-fastapi/app/routers/users.py +46 -0
  368. package/templates/stacks/python-fastapi/app/schemas/__init__.py +0 -0
  369. package/templates/stacks/python-fastapi/app/schemas/user.py +56 -0
  370. package/templates/stacks/python-fastapi/app/services/__init__.py +0 -0
  371. package/templates/stacks/python-fastapi/app/services/auth_service.py +22 -0
  372. package/templates/stacks/python-fastapi/app/services/user_service.py +31 -0
  373. package/templates/stacks/python-fastapi/gitignore +12 -0
  374. package/templates/stacks/python-fastapi/llms.txt.j2 +53 -0
  375. package/templates/stacks/python-fastapi/openapi.json.j2 +43 -0
  376. package/templates/stacks/python-fastapi/pyproject.toml.j2 +45 -0
  377. package/templates/stacks/python-fastapi/tests/__init__.py +0 -0
  378. package/templates/stacks/python-fastapi/tests/test_auth.py +22 -0
  379. package/templates/stacks/ruby-rails-8/.dare/skills.yml +50 -0
  380. package/templates/stacks/ruby-rails-8/.env.example +20 -0
  381. package/templates/stacks/ruby-rails-8/.github/workflows/dare-ci.yml +112 -0
  382. package/templates/stacks/ruby-rails-8/Gemfile.erb +61 -0
  383. package/templates/stacks/ruby-rails-8/app/channels/application_cable/channel.rb +11 -0
  384. package/templates/stacks/ruby-rails-8/app/channels/application_cable/connection.rb +34 -0
  385. package/templates/stacks/ruby-rails-8/app/channels/dare_updates_channel.rb +18 -0
  386. package/templates/stacks/ruby-rails-8/app/channels/user_updates_channel.rb +23 -0
  387. package/templates/stacks/ruby-rails-8/app/controllers/application_controller.rb +44 -0
  388. package/templates/stacks/ruby-rails-8/app/controllers/concerns/problem_details.rb +93 -0
  389. package/templates/stacks/ruby-rails-8/app/handlers/summarize_handler.rb +33 -0
  390. package/templates/stacks/ruby-rails-8/app/handlers/users_handler.rb +68 -0
  391. package/templates/stacks/ruby-rails-8/app/llm/cache/llm_cache.rb +44 -0
  392. package/templates/stacks/ruby-rails-8/app/llm/prompts/prompt_loader.rb +54 -0
  393. package/templates/stacks/ruby-rails-8/app/llm/prompts/summarize_v1.jinja2 +12 -0
  394. package/templates/stacks/ruby-rails-8/app/llm/providers/dummy_provider.rb +35 -0
  395. package/templates/stacks/ruby-rails-8/app/llm/providers/llm_provider.rb +67 -0
  396. package/templates/stacks/ruby-rails-8/app/llm/providers/openai_provider.rb +62 -0
  397. package/templates/stacks/ruby-rails-8/app/llm/rate_limit/token_bucket.rb +82 -0
  398. package/templates/stacks/ruby-rails-8/app/llm/validators/summarize_output_schema.json +21 -0
  399. package/templates/stacks/ruby-rails-8/app/llm/validators/validator.rb +52 -0
  400. package/templates/stacks/ruby-rails-8/app/models/user.rb +36 -0
  401. package/templates/stacks/ruby-rails-8/app/presenters/user_presenter.rb +48 -0
  402. package/templates/stacks/ruby-rails-8/app/repositories/document_repository.rb +57 -0
  403. package/templates/stacks/ruby-rails-8/app/repositories/user_repository.rb +73 -0
  404. package/templates/stacks/ruby-rails-8/app/services/create_user_service.rb +67 -0
  405. package/templates/stacks/ruby-rails-8/app/services/realtime_service.rb +53 -0
  406. package/templates/stacks/ruby-rails-8/app/services/summarize_document_service.rb +57 -0
  407. package/templates/stacks/ruby-rails-8/config/dare.yml +42 -0
  408. package/templates/stacks/ruby-rails-8/config/initializers/dare.rb +31 -0
  409. package/templates/stacks/ruby-rails-8/config/initializers/rack_attack.rb +64 -0
  410. package/templates/stacks/ruby-rails-8/config/initializers/rswag_api.rb +12 -0
  411. package/templates/stacks/ruby-rails-8/lib/tasks/dare.rake +159 -0
  412. package/templates/stacks/ruby-rails-8/llms.txt.erb +69 -0
  413. package/templates/stacks/ruby-rails-8/spec/api/summarize_spec.rb +56 -0
  414. package/templates/stacks/ruby-rails-8/spec/api/users_spec.rb +72 -0
  415. package/templates/stacks/ruby-rails-8/spec/channels/dare_updates_channel_spec.rb +61 -0
  416. package/templates/stacks/ruby-rails-8/spec/channels/user_updates_channel_spec.rb +56 -0
  417. package/templates/stacks/ruby-rails-8/spec/factories/users.rb +27 -0
  418. package/templates/stacks/ruby-rails-8/spec/handlers/users_handler_spec.rb +88 -0
  419. package/templates/stacks/ruby-rails-8/spec/rails_helper.rb +31 -0
  420. package/templates/stacks/ruby-rails-8/spec/services/create_user_service_spec.rb +88 -0
  421. package/templates/stacks/ruby-rails-8/spec/services/summarize_document_service_spec.rb +142 -0
  422. package/templates/stacks/ruby-rails-8/spec/swagger_helper.rb +73 -0
  423. package/templates/stacks/rust-axum/.dare/skills.yml +11 -0
  424. package/templates/stacks/rust-axum/.env.example +26 -0
  425. package/templates/stacks/rust-axum/.github/workflows/dare-ci.yml +40 -0
  426. package/templates/stacks/rust-axum/Cargo.toml.tera +53 -0
  427. package/templates/stacks/rust-axum/README.md.tera +37 -0
  428. package/templates/stacks/rust-axum/gitignore +5 -0
  429. package/templates/stacks/rust-axum/llms.txt.tera +54 -0
  430. package/templates/stacks/rust-axum/migrations/0001_create_users.sql +13 -0
  431. package/templates/stacks/rust-axum/openapi.json.tera +46 -0
  432. package/templates/stacks/rust-axum/src/config.rs +45 -0
  433. package/templates/stacks/rust-axum/src/errors.rs +48 -0
  434. package/templates/stacks/rust-axum/src/handlers/auth.rs +48 -0
  435. package/templates/stacks/rust-axum/src/handlers/mod.rs +3 -0
  436. package/templates/stacks/rust-axum/src/handlers/users.rs +81 -0
  437. package/templates/stacks/rust-axum/src/handlers/ws.rs +24 -0
  438. package/templates/stacks/rust-axum/src/lib.rs +19 -0
  439. package/templates/stacks/rust-axum/src/llm/mod.rs +1 -0
  440. package/templates/stacks/rust-axum/src/llm/provider.rs +48 -0
  441. package/templates/stacks/rust-axum/src/main.rs.tera +64 -0
  442. package/templates/stacks/rust-axum/src/middleware/auth.rs +20 -0
  443. package/templates/stacks/rust-axum/src/middleware/mod.rs +2 -0
  444. package/templates/stacks/rust-axum/src/middleware/rate_limit.rs +27 -0
  445. package/templates/stacks/rust-axum/src/models/mod.rs +1 -0
  446. package/templates/stacks/rust-axum/src/models/user.rs +13 -0
  447. package/templates/stacks/rust-axum/src/repositories/mod.rs +1 -0
  448. package/templates/stacks/rust-axum/src/repositories/user_repository.rs +62 -0
  449. package/templates/stacks/rust-axum/src/services/auth_service.rs +50 -0
  450. package/templates/stacks/rust-axum/src/services/mod.rs +2 -0
  451. package/templates/stacks/rust-axum/src/services/user_service.rs +53 -0
  452. package/templates/stacks/rust-axum/tests/integration_test.rs.tera +13 -0
  453. package/LICENSE +0 -21
@@ -0,0 +1,48 @@
1
+ //! RFC 7807 Problem Details — uniform error shape across the API.
2
+ use axum::{
3
+ http::StatusCode,
4
+ response::{IntoResponse, Response},
5
+ Json,
6
+ };
7
+ use serde_json::json;
8
+ use thiserror::Error;
9
+
10
+ #[derive(Debug, Error)]
11
+ pub enum AppError {
12
+ #[error("invalid credentials")]
13
+ Unauthorized,
14
+ #[error("forbidden")]
15
+ Forbidden,
16
+ #[error("not found")]
17
+ NotFound,
18
+ #[error("validation failed: {0}")]
19
+ Validation(String),
20
+ #[error("conflict: {0}")]
21
+ Conflict(String),
22
+ #[error("internal: {0}")]
23
+ Internal(#[from] anyhow::Error),
24
+ #[error("database: {0}")]
25
+ Db(#[from] sqlx::Error),
26
+ }
27
+
28
+ impl IntoResponse for AppError {
29
+ fn into_response(self) -> Response {
30
+ let (status, title) = match &self {
31
+ AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
32
+ AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden"),
33
+ AppError::NotFound => (StatusCode::NOT_FOUND, "Not Found"),
34
+ AppError::Validation(_) => (StatusCode::BAD_REQUEST, "Bad Request"),
35
+ AppError::Conflict(_) => (StatusCode::CONFLICT, "Conflict"),
36
+ AppError::Internal(_) | AppError::Db(_) => {
37
+ (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
38
+ }
39
+ };
40
+ let body = json!({
41
+ "type": format!("urn:problem:{}", status.as_u16()),
42
+ "title": title,
43
+ "status": status.as_u16(),
44
+ "detail": self.to_string(),
45
+ });
46
+ (status, Json(body)).into_response()
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ //! Auth handlers — POST /auth/login, GET /auth/me.
2
+ use axum::{extract::State, http::HeaderMap, Json};
3
+ use serde::{Deserialize, Serialize};
4
+
5
+ use crate::{
6
+ errors::AppError,
7
+ middleware::auth::extract_bearer,
8
+ services::auth_service,
9
+ services::user_service,
10
+ SharedState,
11
+ };
12
+
13
+ #[derive(Debug, Deserialize)]
14
+ pub struct LoginRequest {
15
+ pub email: String,
16
+ pub password: String,
17
+ }
18
+
19
+ #[derive(Debug, Serialize)]
20
+ pub struct LoginResponse {
21
+ pub access_token: String,
22
+ pub token_type: &'static str,
23
+ pub expires_in: i64,
24
+ }
25
+
26
+ #[derive(Debug, Serialize)]
27
+ pub struct MeResponse {
28
+ pub id: uuid::Uuid,
29
+ pub email: String,
30
+ pub role: String,
31
+ }
32
+
33
+ pub async fn login(
34
+ State(state): State<SharedState>,
35
+ Json(body): Json<LoginRequest>,
36
+ ) -> Result<Json<LoginResponse>, AppError> {
37
+ let (token, expires_in) = auth_service::login(&state, &body.email, &body.password).await?;
38
+ Ok(Json(LoginResponse { access_token: token, token_type: "Bearer", expires_in }))
39
+ }
40
+
41
+ pub async fn me(
42
+ State(state): State<SharedState>,
43
+ headers: HeaderMap,
44
+ ) -> Result<Json<MeResponse>, AppError> {
45
+ let claims = extract_bearer(&state.cfg, &headers)?;
46
+ let user = user_service::find_by_id(&state, claims.sub).await?;
47
+ Ok(Json(MeResponse { id: user.id, email: user.email, role: user.role }))
48
+ }
@@ -0,0 +1,3 @@
1
+ pub mod auth;
2
+ pub mod users;
3
+ pub mod ws;
@@ -0,0 +1,81 @@
1
+ //! Users handlers — GET /users, POST /users.
2
+ use axum::{
3
+ extract::{Query, State},
4
+ http::HeaderMap,
5
+ Json,
6
+ };
7
+ use serde::{Deserialize, Serialize};
8
+
9
+ use crate::{
10
+ errors::AppError,
11
+ middleware::auth::extract_bearer,
12
+ services::user_service,
13
+ SharedState,
14
+ };
15
+
16
+ #[derive(Debug, Deserialize)]
17
+ pub struct PageQuery {
18
+ #[serde(default = "default_page")]
19
+ pub page: i64,
20
+ #[serde(default = "default_limit")]
21
+ pub limit: i64,
22
+ }
23
+
24
+ fn default_page() -> i64 { 1 }
25
+ fn default_limit() -> i64 { 20 }
26
+
27
+ #[derive(Debug, Serialize)]
28
+ pub struct UserOut {
29
+ pub id: uuid::Uuid,
30
+ pub email: String,
31
+ pub role: String,
32
+ pub created_at: chrono::DateTime<chrono::Utc>,
33
+ }
34
+
35
+ #[derive(Debug, Serialize)]
36
+ pub struct UserPage {
37
+ pub items: Vec<UserOut>,
38
+ pub total: i64,
39
+ pub page: i64,
40
+ }
41
+
42
+ #[derive(Debug, Deserialize)]
43
+ pub struct CreateUserRequest {
44
+ pub email: String,
45
+ pub password: String,
46
+ #[serde(default = "default_role")]
47
+ pub role: String,
48
+ }
49
+
50
+ fn default_role() -> String { "USER".to_string() }
51
+
52
+ pub async fn list(
53
+ State(state): State<SharedState>,
54
+ Query(q): Query<PageQuery>,
55
+ headers: HeaderMap,
56
+ ) -> Result<Json<UserPage>, AppError> {
57
+ let _ = extract_bearer(&state.cfg, &headers)?;
58
+ let page = q.page.max(1);
59
+ let limit = q.limit.clamp(1, 100);
60
+ let (items, total) = user_service::page(&state, page, limit).await?;
61
+ Ok(Json(UserPage {
62
+ items: items.into_iter().map(|u| UserOut {
63
+ id: u.id, email: u.email, role: u.role, created_at: u.created_at,
64
+ }).collect(),
65
+ total,
66
+ page,
67
+ }))
68
+ }
69
+
70
+ pub async fn create(
71
+ State(state): State<SharedState>,
72
+ headers: HeaderMap,
73
+ Json(body): Json<CreateUserRequest>,
74
+ ) -> Result<Json<UserOut>, AppError> {
75
+ let claims = extract_bearer(&state.cfg, &headers)?;
76
+ if claims.role != "ADMIN" {
77
+ return Err(AppError::Forbidden);
78
+ }
79
+ let user = user_service::create(&state, &body.email, &body.password, &body.role).await?;
80
+ Ok(Json(UserOut { id: user.id, email: user.email, role: user.role, created_at: user.created_at }))
81
+ }
@@ -0,0 +1,24 @@
1
+ //! WebSocket echo handler using axum::extract::ws.
2
+ use axum::{
3
+ extract::ws::{Message, WebSocket, WebSocketUpgrade},
4
+ response::IntoResponse,
5
+ };
6
+
7
+ pub async fn handler(ws: WebSocketUpgrade) -> impl IntoResponse {
8
+ ws.on_upgrade(echo)
9
+ }
10
+
11
+ async fn echo(mut socket: WebSocket) {
12
+ while let Some(Ok(msg)) = socket.recv().await {
13
+ match msg {
14
+ Message::Text(t) => {
15
+ let _ = socket.send(Message::Text(t)).await;
16
+ }
17
+ Message::Binary(b) => {
18
+ let _ = socket.send(Message::Binary(b)).await;
19
+ }
20
+ Message::Close(_) => break,
21
+ _ => {}
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,19 @@
1
+ //! Public crate surface — re-exported for tests and bins.
2
+ pub mod config;
3
+ pub mod errors;
4
+ pub mod handlers;
5
+ pub mod llm;
6
+ pub mod middleware;
7
+ pub mod models;
8
+ pub mod repositories;
9
+ pub mod services;
10
+
11
+ use std::sync::Arc;
12
+
13
+ #[derive(Clone)]
14
+ pub struct AppState {
15
+ pub pool: sqlx::PgPool,
16
+ pub cfg: config::Config,
17
+ }
18
+
19
+ pub type SharedState = Arc<AppState>;
@@ -0,0 +1 @@
1
+ pub mod provider;
@@ -0,0 +1,48 @@
1
+ //! LLM provider abstraction. Two implementations: Dummy and OpenAi.
2
+ //!
3
+ //! `async-trait` is used for dyn-compatible async traits — see Cargo.toml.
4
+ use async_trait::async_trait;
5
+
6
+ #[async_trait]
7
+ pub trait LlmProvider: Send + Sync {
8
+ async fn complete(&self, prompt: &str, max_tokens: usize) -> anyhow::Result<String>;
9
+ }
10
+
11
+ pub struct DummyProvider;
12
+
13
+ #[async_trait]
14
+ impl LlmProvider for DummyProvider {
15
+ async fn complete(&self, prompt: &str, max_tokens: usize) -> anyhow::Result<String> {
16
+ let n = prompt.len().min(max_tokens);
17
+ Ok(format!("[dummy] {}", &prompt[..n]))
18
+ }
19
+ }
20
+
21
+ pub struct OpenAiProvider {
22
+ pub api_key: String,
23
+ pub model: String,
24
+ pub base_uri: String,
25
+ }
26
+
27
+ #[async_trait]
28
+ impl LlmProvider for OpenAiProvider {
29
+ async fn complete(&self, prompt: &str, max_tokens: usize) -> anyhow::Result<String> {
30
+ let client = reqwest::Client::new();
31
+ let body = serde_json::json!({
32
+ "model": self.model,
33
+ "messages": [{"role": "user", "content": prompt}],
34
+ "max_tokens": max_tokens
35
+ });
36
+ let res = client
37
+ .post(format!("{}/chat/completions", self.base_uri))
38
+ .bearer_auth(&self.api_key)
39
+ .json(&body)
40
+ .send()
41
+ .await?;
42
+ let v: serde_json::Value = res.json().await?;
43
+ Ok(v["choices"][0]["message"]["content"]
44
+ .as_str()
45
+ .unwrap_or_default()
46
+ .to_string())
47
+ }
48
+ }
@@ -0,0 +1,64 @@
1
+ //! {{ projectName }} — Axum API entrypoint.
2
+ use std::{net::SocketAddr, sync::Arc};
3
+
4
+ use axum::{routing::{get, post}, Router};
5
+ use sqlx::postgres::PgPoolOptions;
6
+ use tower_http::{cors::CorsLayer, trace::TraceLayer};
7
+ use tracing_subscriber::EnvFilter;
8
+
9
+ use {{ crateName }}::{
10
+ config::Config,
11
+ handlers::{auth, users, ws},
12
+ middleware::rate_limit::with_rate_limit,
13
+ AppState,
14
+ };
15
+
16
+ #[tokio::main]
17
+ async fn main() -> anyhow::Result<()> {
18
+ dotenvy::dotenv().ok();
19
+ tracing_subscriber::fmt()
20
+ .with_env_filter(EnvFilter::from_default_env())
21
+ .init();
22
+
23
+ let cfg = Config::from_env()?;
24
+
25
+ let pool = PgPoolOptions::new()
26
+ .max_connections(10)
27
+ .connect(&cfg.database_url)
28
+ .await?;
29
+
30
+ sqlx::migrate!("./migrations").run(&pool).await?;
31
+
32
+ let state = Arc::new(AppState { pool, cfg: cfg.clone() });
33
+
34
+ let cors = CorsLayer::new()
35
+ .allow_origin(cfg.cors_layer_origins())
36
+ .allow_methods(tower_http::cors::Any)
37
+ .allow_headers(tower_http::cors::Any);
38
+
39
+ let app = Router::new()
40
+ .route("/healthz", get(|| async { "ok" }))
41
+ .route("/auth/login", post(auth::login))
42
+ .route("/auth/me", get(auth::me))
43
+ .route("/users", get(users::list).post(users::create))
44
+ .route("/ws", get(ws::handler))
45
+ .route("/openapi.json", get(serve_openapi))
46
+ .layer(cors)
47
+ .layer(TraceLayer::new_for_http())
48
+ .with_state(state);
49
+
50
+ // Rate limit applied via helper (avoids naming GovernorLayer generics).
51
+ let app = with_rate_limit(app, &cfg);
52
+
53
+ let addr: SocketAddr = ([0, 0, 0, 0], cfg.app_port).into();
54
+ tracing::info!("listening on {}", addr);
55
+ let listener = tokio::net::TcpListener::bind(addr).await?;
56
+ axum::serve(listener, app).await?;
57
+ Ok(())
58
+ }
59
+
60
+ async fn serve_openapi() -> axum::response::Json<serde_json::Value> {
61
+ let raw = include_str!("../openapi.json");
62
+ let v: serde_json::Value = serde_json::from_str(raw).unwrap_or_default();
63
+ axum::response::Json(v)
64
+ }
@@ -0,0 +1,20 @@
1
+ //! Bearer JWT extractor — reads Authorization header and validates the token.
2
+ use axum::http::{header::AUTHORIZATION, HeaderMap};
3
+ use jsonwebtoken::{decode, DecodingKey, Validation};
4
+
5
+ use crate::{config::Config, errors::AppError, services::auth_service::Claims};
6
+
7
+ pub fn extract_bearer(cfg: &Config, headers: &HeaderMap) -> Result<Claims, AppError> {
8
+ let raw = headers
9
+ .get(AUTHORIZATION)
10
+ .and_then(|v| v.to_str().ok())
11
+ .ok_or(AppError::Unauthorized)?;
12
+ let token = raw.strip_prefix("Bearer ").ok_or(AppError::Unauthorized)?;
13
+ let data = decode::<Claims>(
14
+ token,
15
+ &DecodingKey::from_secret(cfg.jwt_secret.as_bytes()),
16
+ &Validation::default(),
17
+ )
18
+ .map_err(|_| AppError::Unauthorized)?;
19
+ Ok(data.claims)
20
+ }
@@ -0,0 +1,2 @@
1
+ pub mod auth;
2
+ pub mod rate_limit;
@@ -0,0 +1,27 @@
1
+ //! Rate limit via tower_governor — per-IP token bucket.
2
+ //!
3
+ //! We apply the layer through a generic helper instead of naming the
4
+ //! `GovernorLayer<K, M>` generics explicitly — the concrete extractor /
5
+ //! middleware types vary across tower_governor versions, so we let type
6
+ //! inference fill them in.
7
+ use std::sync::Arc;
8
+
9
+ use axum::Router;
10
+ use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer};
11
+
12
+ use crate::config::Config;
13
+
14
+ /// Apply the per-IP rate limit layer to a router.
15
+ pub fn with_rate_limit<S>(router: Router<S>, cfg: &Config) -> Router<S>
16
+ where
17
+ S: Clone + Send + Sync + 'static,
18
+ {
19
+ let conf = Arc::new(
20
+ GovernorConfigBuilder::default()
21
+ .per_second(cfg.rate_limit_per_sec)
22
+ .burst_size(cfg.rate_limit_burst)
23
+ .finish()
24
+ .expect("valid governor config"),
25
+ );
26
+ router.layer(GovernorLayer { config: conf })
27
+ }
@@ -0,0 +1 @@
1
+ pub mod user;
@@ -0,0 +1,13 @@
1
+ //! User domain model. sqlx FromRow + serde Serialize.
2
+ use serde::{Deserialize, Serialize};
3
+ use sqlx::FromRow;
4
+
5
+ #[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
6
+ pub struct User {
7
+ pub id: uuid::Uuid,
8
+ pub email: String,
9
+ pub password: String,
10
+ pub role: String,
11
+ pub created_at: chrono::DateTime<chrono::Utc>,
12
+ pub updated_at: chrono::DateTime<chrono::Utc>,
13
+ }
@@ -0,0 +1 @@
1
+ pub mod user_repository;
@@ -0,0 +1,62 @@
1
+ //! User repository — the only place sqlx is called for the User aggregate.
2
+ use sqlx::PgPool;
3
+ use uuid::Uuid;
4
+
5
+ use crate::models::user::User;
6
+
7
+ pub async fn find_by_email(pool: &PgPool, email: &str) -> Result<Option<User>, sqlx::Error> {
8
+ sqlx::query_as::<_, User>(
9
+ "SELECT id, email, password, role, created_at, updated_at \
10
+ FROM users WHERE email = $1",
11
+ )
12
+ .bind(email)
13
+ .fetch_optional(pool)
14
+ .await
15
+ }
16
+
17
+ pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result<Option<User>, sqlx::Error> {
18
+ sqlx::query_as::<_, User>(
19
+ "SELECT id, email, password, role, created_at, updated_at \
20
+ FROM users WHERE id = $1",
21
+ )
22
+ .bind(id)
23
+ .fetch_optional(pool)
24
+ .await
25
+ }
26
+
27
+ pub async fn page(pool: &PgPool, offset: i64, limit: i64) -> Result<Vec<User>, sqlx::Error> {
28
+ sqlx::query_as::<_, User>(
29
+ "SELECT id, email, password, role, created_at, updated_at \
30
+ FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2",
31
+ )
32
+ .bind(limit)
33
+ .bind(offset)
34
+ .fetch_all(pool)
35
+ .await
36
+ }
37
+
38
+ pub async fn count(pool: &PgPool) -> Result<i64, sqlx::Error> {
39
+ let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users")
40
+ .fetch_one(pool)
41
+ .await?;
42
+ Ok(row.0)
43
+ }
44
+
45
+ pub async fn create(
46
+ pool: &PgPool,
47
+ email: &str,
48
+ password_hash: &str,
49
+ role: &str,
50
+ ) -> Result<User, sqlx::Error> {
51
+ sqlx::query_as::<_, User>(
52
+ "INSERT INTO users (id, email, password, role) \
53
+ VALUES ($1, $2, $3, $4) \
54
+ RETURNING id, email, password, role, created_at, updated_at",
55
+ )
56
+ .bind(Uuid::new_v4())
57
+ .bind(email)
58
+ .bind(password_hash)
59
+ .bind(role)
60
+ .fetch_one(pool)
61
+ .await
62
+ }
@@ -0,0 +1,50 @@
1
+ //! Auth business logic — login flow.
2
+ use argon2::{
3
+ password_hash::{PasswordHash, PasswordVerifier},
4
+ Argon2,
5
+ };
6
+ use chrono::Utc;
7
+ use jsonwebtoken::{encode, EncodingKey, Header};
8
+ use serde::{Deserialize, Serialize};
9
+
10
+ use crate::{errors::AppError, repositories::user_repository, SharedState};
11
+
12
+ #[derive(Debug, Serialize, Deserialize)]
13
+ pub struct Claims {
14
+ pub sub: uuid::Uuid,
15
+ pub email: String,
16
+ pub role: String,
17
+ pub exp: i64,
18
+ }
19
+
20
+ pub async fn login(
21
+ state: &SharedState,
22
+ email: &str,
23
+ password: &str,
24
+ ) -> Result<(String, i64), AppError> {
25
+ let email = email.trim().to_lowercase();
26
+ let user = user_repository::find_by_email(&state.pool, &email).await?
27
+ .ok_or(AppError::Unauthorized)?;
28
+
29
+ let parsed = PasswordHash::new(&user.password)
30
+ .map_err(|_| AppError::Unauthorized)?;
31
+ Argon2::default()
32
+ .verify_password(password.as_bytes(), &parsed)
33
+ .map_err(|_| AppError::Unauthorized)?;
34
+
35
+ let exp = Utc::now().timestamp() + state.cfg.jwt_expires_secs;
36
+ let claims = Claims {
37
+ sub: user.id,
38
+ email: user.email.clone(),
39
+ role: user.role.clone(),
40
+ exp,
41
+ };
42
+ let token = encode(
43
+ &Header::default(),
44
+ &claims,
45
+ &EncodingKey::from_secret(state.cfg.jwt_secret.as_bytes()),
46
+ )
47
+ .map_err(|e| AppError::Internal(anyhow::anyhow!(e)))?;
48
+
49
+ Ok((token, state.cfg.jwt_expires_secs))
50
+ }
@@ -0,0 +1,2 @@
1
+ pub mod auth_service;
2
+ pub mod user_service;
@@ -0,0 +1,53 @@
1
+ //! User business logic — find / page / create.
2
+ use argon2::{
3
+ password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
4
+ Argon2,
5
+ };
6
+ use uuid::Uuid;
7
+
8
+ use crate::{
9
+ errors::AppError,
10
+ models::user::User,
11
+ repositories::user_repository,
12
+ SharedState,
13
+ };
14
+
15
+ pub async fn find_by_id(state: &SharedState, id: Uuid) -> Result<User, AppError> {
16
+ user_repository::find_by_id(&state.pool, id).await?
17
+ .ok_or(AppError::NotFound)
18
+ }
19
+
20
+ pub async fn page(state: &SharedState, page: i64, limit: i64) -> Result<(Vec<User>, i64), AppError> {
21
+ let offset = (page - 1) * limit;
22
+ let items = user_repository::page(&state.pool, offset, limit).await?;
23
+ let total = user_repository::count(&state.pool).await?;
24
+ Ok((items, total))
25
+ }
26
+
27
+ pub async fn create(
28
+ state: &SharedState,
29
+ email: &str,
30
+ password: &str,
31
+ role: &str,
32
+ ) -> Result<User, AppError> {
33
+ if !["USER", "ADMIN"].contains(&role) {
34
+ return Err(AppError::Validation(format!("role '{}' is not allowed", role)));
35
+ }
36
+ if password.len() < 8 {
37
+ return Err(AppError::Validation("password must be at least 8 chars".into()));
38
+ }
39
+
40
+ let email = email.trim().to_lowercase();
41
+ if user_repository::find_by_email(&state.pool, &email).await?.is_some() {
42
+ return Err(AppError::Conflict("email already in use".into()));
43
+ }
44
+
45
+ let salt = SaltString::generate(&mut OsRng);
46
+ let hash = Argon2::default()
47
+ .hash_password(password.as_bytes(), &salt)
48
+ .map_err(|e| AppError::Internal(anyhow::anyhow!(e.to_string())))?
49
+ .to_string();
50
+
51
+ let user = user_repository::create(&state.pool, &email, &hash, role).await?;
52
+ Ok(user)
53
+ }
@@ -0,0 +1,13 @@
1
+ //! Smoke integration test. Exercises pure functions that don't need a DB.
2
+ //!
3
+ //! Full HTTP tests require a Postgres instance and live binding — keep those
4
+ //! gated behind `#[ignore]` so `cargo test` stays fast in CI without infra.
5
+
6
+ #[test]
7
+ fn config_defaults_are_sane() {
8
+ std::env::set_var("DATABASE_URL", "postgres://user:pass@localhost:5432/x");
9
+ let cfg = {{ crateName }}::config::Config::from_env().expect("config");
10
+ assert_eq!(cfg.app_port, 8000);
11
+ assert!(cfg.rate_limit_per_sec > 0);
12
+ assert!(cfg.rate_limit_burst >= cfg.rate_limit_per_sec as u32);
13
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Dewtech Technologies
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.