@aurostack/stacks 0.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 (601) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/cli/src/extract.mjs +165 -0
  4. package/cli/src/fsx.mjs +197 -0
  5. package/cli/src/generate.mjs +235 -0
  6. package/cli/src/hooks.mjs +55 -0
  7. package/cli/src/manifest.mjs +163 -0
  8. package/cli/src/strip.mjs +208 -0
  9. package/cli/src/tokens.mjs +102 -0
  10. package/cli/stack.mjs +672 -0
  11. package/package.json +37 -0
  12. package/templates/nest-api/files/.bin/db.ts +123 -0
  13. package/templates/nest-api/files/.bin/dc.ts +74 -0
  14. package/templates/nest-api/files/.bin/helpers.ts +49 -0
  15. package/templates/nest-api/files/.bin/secrets.ts +68 -0
  16. package/templates/nest-api/files/.bin/start.ts +47 -0
  17. package/templates/nest-api/files/.dockerignore +7 -0
  18. package/templates/nest-api/files/.env.example +91 -0
  19. package/templates/nest-api/files/.env.test.example +75 -0
  20. package/templates/nest-api/files/.forgejo/workflows/build-and-push.yml +41 -0
  21. package/templates/nest-api/files/.forgejo/workflows/test-e2e.yml +83 -0
  22. package/templates/nest-api/files/.forgejo/workflows/test.yml +53 -0
  23. package/templates/nest-api/files/.npmrc.example +1 -0
  24. package/templates/nest-api/files/.nvmrc +1 -0
  25. package/templates/nest-api/files/.prettierrc +8 -0
  26. package/templates/nest-api/files/.swcrc +15 -0
  27. package/templates/nest-api/files/.watchmanconfig +3 -0
  28. package/templates/nest-api/files/.yarnrc.yml +1 -0
  29. package/templates/nest-api/files/Dockerfile +40 -0
  30. package/templates/nest-api/files/__mocks__/activation-twofa.js +7 -0
  31. package/templates/nest-api/files/__mocks__/activation.js +7 -0
  32. package/templates/nest-api/files/__mocks__/forgot-password.js +7 -0
  33. package/templates/nest-api/files/__mocks__/main-layout.js +7 -0
  34. package/templates/nest-api/files/__mocks__/password-changed.js +7 -0
  35. package/templates/nest-api/files/__mocks__/reset-password.js +7 -0
  36. package/templates/nest-api/files/__mocks__/set-password.js +7 -0
  37. package/templates/nest-api/files/__mocks__/welcome.js +7 -0
  38. package/templates/nest-api/files/_gitignore +62 -0
  39. package/templates/nest-api/files/compose.test.yml +33 -0
  40. package/templates/nest-api/files/compose.yml +39 -0
  41. package/templates/nest-api/files/emails/activation-twofa.tsx +58 -0
  42. package/templates/nest-api/files/emails/activation.tsx +36 -0
  43. package/templates/nest-api/files/emails/forgot-password.tsx +29 -0
  44. package/templates/nest-api/files/emails/main-layout.tsx +134 -0
  45. package/templates/nest-api/files/emails/password-changed.tsx +24 -0
  46. package/templates/nest-api/files/emails/reset-password.tsx +24 -0
  47. package/templates/nest-api/files/emails/set-password.tsx +25 -0
  48. package/templates/nest-api/files/emails/welcome.tsx +39 -0
  49. package/templates/nest-api/files/eslint.config.mjs +28 -0
  50. package/templates/nest-api/files/global.d.ts +5 -0
  51. package/templates/nest-api/files/nest-cli.json +8 -0
  52. package/templates/nest-api/files/package.json +150 -0
  53. package/templates/nest-api/files/prisma/schema/auth.prisma +75 -0
  54. package/templates/nest-api/files/prisma/schema/base.prisma +17 -0
  55. package/templates/nest-api/files/prisma/schema/enum.prisma +17 -0
  56. package/templates/nest-api/files/prisma/schema/notification.prisma +14 -0
  57. package/templates/nest-api/files/prisma.config.ts +13 -0
  58. package/templates/nest-api/files/public/.gitkeep +0 -0
  59. package/templates/nest-api/files/seeders/index.ts +27 -0
  60. package/templates/nest-api/files/seeders/util.ts +88 -0
  61. package/templates/nest-api/files/src/__tests__/app.controller.spec.ts +29 -0
  62. package/templates/nest-api/files/src/app.controller.ts +40 -0
  63. package/templates/nest-api/files/src/app.module.ts +268 -0
  64. package/templates/nest-api/files/src/app.resolver.ts +32 -0
  65. package/templates/nest-api/files/src/app.setup.ts +178 -0
  66. package/templates/nest-api/files/src/common/__tests__/auth.service.spec.ts +31 -0
  67. package/templates/nest-api/files/src/common/__tests__/cache.service.spec.ts +214 -0
  68. package/templates/nest-api/files/src/common/__tests__/config.service.spec.ts +56 -0
  69. package/templates/nest-api/files/src/common/__tests__/generator.service.spec.ts +114 -0
  70. package/templates/nest-api/files/src/common/__tests__/health.controller.spec.ts +72 -0
  71. package/templates/nest-api/files/src/common/__tests__/helpers.spec.ts +124 -0
  72. package/templates/nest-api/files/src/common/__tests__/mail.service.spec.ts +75 -0
  73. package/templates/nest-api/files/src/common/__tests__/prisma.indicator.spec.ts +39 -0
  74. package/templates/nest-api/files/src/common/__tests__/prisma.service.spec.ts +15 -0
  75. package/templates/nest-api/files/src/common/__tests__/records.spec.ts +87 -0
  76. package/templates/nest-api/files/src/common/__tests__/schemas.spec.ts +142 -0
  77. package/templates/nest-api/files/src/common/__tests__/throttler-storage.service.spec.ts +80 -0
  78. package/templates/nest-api/files/src/common/common.module.ts +72 -0
  79. package/templates/nest-api/files/src/common/constants/index.ts +2 -0
  80. package/templates/nest-api/files/src/common/constants/options.ts +52 -0
  81. package/templates/nest-api/files/src/common/constants/pino.ts +24 -0
  82. package/templates/nest-api/files/src/common/controllers/health.controller.ts +54 -0
  83. package/templates/nest-api/files/src/common/controllers/index.ts +2 -0
  84. package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +21 -0
  85. package/templates/nest-api/files/src/common/decorators/index.ts +2 -0
  86. package/templates/nest-api/files/src/common/decorators/misc.decorator.ts +49 -0
  87. package/templates/nest-api/files/src/common/decorators/multi-part.decorator.ts +6 -0
  88. package/templates/nest-api/files/src/common/dto/filters.dto.ts +25 -0
  89. package/templates/nest-api/files/src/common/dto/index.ts +1 -0
  90. package/templates/nest-api/files/src/common/entity/index.ts +2 -0
  91. package/templates/nest-api/files/src/common/entity/ok.entity.ts +11 -0
  92. package/templates/nest-api/files/src/common/entity/pagination.entity.ts +24 -0
  93. package/templates/nest-api/files/src/common/exceptions/index.ts +1 -0
  94. package/templates/nest-api/files/src/common/exceptions/prisma.exception.ts +57 -0
  95. package/templates/nest-api/files/src/common/guards/feature-flag.guard.ts +51 -0
  96. package/templates/nest-api/files/src/common/guards/index.ts +1 -0
  97. package/templates/nest-api/files/src/common/interceptors/index.ts +1 -0
  98. package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +37 -0
  99. package/templates/nest-api/files/src/common/misc/helpers.ts +26 -0
  100. package/templates/nest-api/files/src/common/misc/index.ts +3 -0
  101. package/templates/nest-api/files/src/common/misc/origin-builder.ts +56 -0
  102. package/templates/nest-api/files/src/common/misc/prisma.indicator.ts +24 -0
  103. package/templates/nest-api/files/src/common/modules/index.ts +3 -0
  104. package/templates/nest-api/files/src/common/modules/logger.module.ts +104 -0
  105. package/templates/nest-api/files/src/common/modules/metrics.module.ts +28 -0
  106. package/templates/nest-api/files/src/common/modules/queue.module.ts +23 -0
  107. package/templates/nest-api/files/src/common/processors/index.ts +1 -0
  108. package/templates/nest-api/files/src/common/processors/mail/index.ts +47 -0
  109. package/templates/nest-api/files/src/common/processors/mail/process.ts +225 -0
  110. package/templates/nest-api/files/src/common/schemas/fields.ts +76 -0
  111. package/templates/nest-api/files/src/common/schemas/index.ts +1 -0
  112. package/templates/nest-api/files/src/common/services/auth-rate-limit.storage.ts +79 -0
  113. package/templates/nest-api/files/src/common/services/auth.service.ts +16 -0
  114. package/templates/nest-api/files/src/common/services/cache.service.ts +118 -0
  115. package/templates/nest-api/files/src/common/services/config.service.ts +388 -0
  116. package/templates/nest-api/files/src/common/services/feature-flag.service.ts +117 -0
  117. package/templates/nest-api/files/src/common/services/generator.service.ts +54 -0
  118. package/templates/nest-api/files/src/common/services/index.ts +10 -0
  119. package/templates/nest-api/files/src/common/services/logger.service.ts +14 -0
  120. package/templates/nest-api/files/src/common/services/mail.service.ts +44 -0
  121. package/templates/nest-api/files/src/common/services/prisma.service.ts +106 -0
  122. package/templates/nest-api/files/src/common/services/throttler-storage.service.ts +112 -0
  123. package/templates/nest-api/files/src/common/types/email.ts +55 -0
  124. package/templates/nest-api/files/src/common/types/index.ts +3 -0
  125. package/templates/nest-api/files/src/common/types/misc.ts +20 -0
  126. package/templates/nest-api/files/src/common/types/prisma.ts +18 -0
  127. package/templates/nest-api/files/src/common/utils/enum.ts +39 -0
  128. package/templates/nest-api/files/src/common/utils/helpers.ts +139 -0
  129. package/templates/nest-api/files/src/common/utils/index.ts +5 -0
  130. package/templates/nest-api/files/src/common/utils/ip.ts +22 -0
  131. package/templates/nest-api/files/src/common/utils/records.ts +66 -0
  132. package/templates/nest-api/files/src/common/utils/redis.keys.ts +31 -0
  133. package/templates/nest-api/files/src/lib/__tests__/access.spec.ts +33 -0
  134. package/templates/nest-api/files/src/lib/access.ts +114 -0
  135. package/templates/nest-api/files/src/lib/auth.ts +76 -0
  136. package/templates/nest-api/files/src/lib/rate-limit.ts +20 -0
  137. package/templates/nest-api/files/src/main.ts +28 -0
  138. package/templates/nest-api/files/src/media/__tests__/media.service.spec.ts +60 -0
  139. package/templates/nest-api/files/src/media/__tests__/s3.service.spec.ts +159 -0
  140. package/templates/nest-api/files/src/media/entity/file.entity.ts +7 -0
  141. package/templates/nest-api/files/src/media/entity/index.ts +1 -0
  142. package/templates/nest-api/files/src/media/media.module.ts +20 -0
  143. package/templates/nest-api/files/src/media/processors/index.ts +44 -0
  144. package/templates/nest-api/files/src/media/services/index.ts +2 -0
  145. package/templates/nest-api/files/src/media/services/media.service.ts +29 -0
  146. package/templates/nest-api/files/src/media/services/s3.service.ts +168 -0
  147. package/templates/nest-api/files/src/notifications/controllers/index.ts +1 -0
  148. package/templates/nest-api/files/src/notifications/controllers/notifications.controller.ts +72 -0
  149. package/templates/nest-api/files/src/notifications/dto/index.ts +1 -0
  150. package/templates/nest-api/files/src/notifications/dto/notification.dto.ts +11 -0
  151. package/templates/nest-api/files/src/notifications/entity/index.ts +1 -0
  152. package/templates/nest-api/files/src/notifications/entity/notification.entity.ts +37 -0
  153. package/templates/nest-api/files/src/notifications/notifications.module.ts +11 -0
  154. package/templates/nest-api/files/src/notifications/services/index.ts +1 -0
  155. package/templates/nest-api/files/src/notifications/services/notifications.service.ts +98 -0
  156. package/templates/nest-api/files/src/realtime/__tests__/events.gateway.spec.ts +88 -0
  157. package/templates/nest-api/files/src/realtime/__tests__/rooms.spec.ts +29 -0
  158. package/templates/nest-api/files/src/realtime/__tests__/ws-permissions.guard.spec.ts +54 -0
  159. package/templates/nest-api/files/src/realtime/adapters/redis-io.adapter.ts +57 -0
  160. package/templates/nest-api/files/src/realtime/constants/index.ts +68 -0
  161. package/templates/nest-api/files/src/realtime/decorators/index.ts +1 -0
  162. package/templates/nest-api/files/src/realtime/decorators/ws-permissions.decorator.ts +14 -0
  163. package/templates/nest-api/files/src/realtime/gateways/events.gateway.ts +187 -0
  164. package/templates/nest-api/files/src/realtime/guards/index.ts +2 -0
  165. package/templates/nest-api/files/src/realtime/guards/ws-auth.guard.ts +20 -0
  166. package/templates/nest-api/files/src/realtime/guards/ws-permissions.guard.ts +35 -0
  167. package/templates/nest-api/files/src/realtime/misc/session-from-socket.ts +54 -0
  168. package/templates/nest-api/files/src/realtime/realtime.module.ts +26 -0
  169. package/templates/nest-api/files/src/realtime/services/presence.service.ts +56 -0
  170. package/templates/nest-api/files/src/realtime/services/realtime.service.ts +72 -0
  171. package/templates/nest-api/files/src/users/__tests__/current-user.controller.spec.ts +77 -0
  172. package/templates/nest-api/files/src/users/__tests__/current-user.service.spec.ts +159 -0
  173. package/templates/nest-api/files/src/users/__tests__/user.entity.spec.ts +175 -0
  174. package/templates/nest-api/files/src/users/__tests__/users.controller.spec.ts +89 -0
  175. package/templates/nest-api/files/src/users/__tests__/users.service.spec.ts +398 -0
  176. package/templates/nest-api/files/src/users/controllers/current-user.controller.ts +82 -0
  177. package/templates/nest-api/files/src/users/controllers/index.ts +2 -0
  178. package/templates/nest-api/files/src/users/controllers/users.controller.ts +40 -0
  179. package/templates/nest-api/files/src/users/decorators/index.ts +1 -0
  180. package/templates/nest-api/files/src/users/decorators/permissions.decorator.ts +24 -0
  181. package/templates/nest-api/files/src/users/dto/avatar.dto.ts +13 -0
  182. package/templates/nest-api/files/src/users/dto/filters.dto.ts +8 -0
  183. package/templates/nest-api/files/src/users/dto/index.ts +2 -0
  184. package/templates/nest-api/files/src/users/entity/index.ts +1 -0
  185. package/templates/nest-api/files/src/users/entity/user.entity.ts +53 -0
  186. package/templates/nest-api/files/src/users/services/current-user.service.ts +46 -0
  187. package/templates/nest-api/files/src/users/services/index.ts +2 -0
  188. package/templates/nest-api/files/src/users/services/users.service.ts +88 -0
  189. package/templates/nest-api/files/src/users/users.module.ts +10 -0
  190. package/templates/nest-api/files/test/app.spec.ts +102 -0
  191. package/templates/nest-api/files/test/auth.spec.ts +108 -0
  192. package/templates/nest-api/files/test/current-user.spec.ts +154 -0
  193. package/templates/nest-api/files/test/factory/app.ts +172 -0
  194. package/templates/nest-api/files/test/factory/constants.ts +22 -0
  195. package/templates/nest-api/files/test/factory/dto.ts +27 -0
  196. package/templates/nest-api/files/test/factory/entity.ts +70 -0
  197. package/templates/nest-api/files/test/factory/gen.ts +75 -0
  198. package/templates/nest-api/files/test/factory/util.ts +21 -0
  199. package/templates/nest-api/files/test/notifications.spec.ts +176 -0
  200. package/templates/nest-api/files/test/rate-limit.spec.ts +180 -0
  201. package/templates/nest-api/files/test/setup.ts +1 -0
  202. package/templates/nest-api/files/test/users.spec.ts +437 -0
  203. package/templates/nest-api/files/tsconfig.build.json +13 -0
  204. package/templates/nest-api/files/tsconfig.json +35 -0
  205. package/templates/nest-api/files/vitest.config.ts +53 -0
  206. package/templates/nest-api/template.json +452 -0
  207. package/templates/node-worker/files/.bin/db.ts +123 -0
  208. package/templates/node-worker/files/.bin/helpers.ts +49 -0
  209. package/templates/node-worker/files/.bin/secrets.ts +68 -0
  210. package/templates/node-worker/files/.dockerignore +5 -0
  211. package/templates/node-worker/files/.env.example +21 -0
  212. package/templates/node-worker/files/.forgejo/workflows/build-and-push.yml +41 -0
  213. package/templates/node-worker/files/.nvmrc +1 -0
  214. package/templates/node-worker/files/.prettierignore +4 -0
  215. package/templates/node-worker/files/.prettierrc +8 -0
  216. package/templates/node-worker/files/.yarnrc.yml +5 -0
  217. package/templates/node-worker/files/Dockerfile +63 -0
  218. package/templates/node-worker/files/Dockerfile.dev +59 -0
  219. package/templates/node-worker/files/_gitignore +48 -0
  220. package/templates/node-worker/files/ecosystem.config.cjs +10 -0
  221. package/templates/node-worker/files/eslint.config.js +37 -0
  222. package/templates/node-worker/files/package.json +67 -0
  223. package/templates/node-worker/files/prisma/_gitignore +1 -0
  224. package/templates/node-worker/files/prisma/schema/base.prisma +36 -0
  225. package/templates/node-worker/files/prisma.config.ts +13 -0
  226. package/templates/node-worker/files/src/config.ts +55 -0
  227. package/templates/node-worker/files/src/db.ts +118 -0
  228. package/templates/node-worker/files/src/index.ts +7 -0
  229. package/templates/node-worker/files/src/logger.ts +45 -0
  230. package/templates/node-worker/files/src/types.ts +23 -0
  231. package/templates/node-worker/files/src/utils/browser.ts +16 -0
  232. package/templates/node-worker/files/src/utils/date.ts +68 -0
  233. package/templates/node-worker/files/src/utils/helpers.ts +17 -0
  234. package/templates/node-worker/files/src/utils/number.ts +47 -0
  235. package/templates/node-worker/files/src/utils/worker.ts +29 -0
  236. package/templates/node-worker/files/src/workers/constants.ts +6 -0
  237. package/templates/node-worker/files/src/workers/example/index.ts +27 -0
  238. package/templates/node-worker/files/src/workers/index.ts +1 -0
  239. package/templates/node-worker/files/tsconfig.json +30 -0
  240. package/templates/node-worker/template.json +135 -0
  241. package/templates/py-worker/files/.dockerignore +176 -0
  242. package/templates/py-worker/files/.env.example +20 -0
  243. package/templates/py-worker/files/.forgejo/workflows/build-amd-push.yml +39 -0
  244. package/templates/py-worker/files/Dockerfile +10 -0
  245. package/templates/py-worker/files/README.md +3 -0
  246. package/templates/py-worker/files/_gitignore +176 -0
  247. package/templates/py-worker/files/config.py +89 -0
  248. package/templates/py-worker/files/models/decorators.py +42 -0
  249. package/templates/py-worker/files/models/enums.py +32 -0
  250. package/templates/py-worker/files/models/mixins.py +61 -0
  251. package/templates/py-worker/files/requirements.txt +18 -0
  252. package/templates/py-worker/files/utils/db.py +67 -0
  253. package/templates/py-worker/files/utils/logger.py +97 -0
  254. package/templates/py-worker/files/utils/misc.py +45 -0
  255. package/templates/py-worker/files/utils/worker.py +80 -0
  256. package/templates/py-worker/files/worker.py +41 -0
  257. package/templates/py-worker/files/workers/hello/process.py +22 -0
  258. package/templates/py-worker/template.json +87 -0
  259. package/templates/react-app/README.md +78 -0
  260. package/templates/react-app/derive.sh +128 -0
  261. package/templates/react-app/files/.bin/openapi.js +32 -0
  262. package/templates/react-app/files/.bin/secrets.js +47 -0
  263. package/templates/react-app/files/.dockerignore +16 -0
  264. package/templates/react-app/files/.editorconfig +12 -0
  265. package/templates/react-app/files/.env.example +34 -0
  266. package/templates/react-app/files/.forgejo/workflows/build-and-push.yml +55 -0
  267. package/templates/react-app/files/.forgejo/workflows/ci.yml +39 -0
  268. package/templates/react-app/files/.husky/pre-commit +1 -0
  269. package/templates/react-app/files/.nvmrc +1 -0
  270. package/templates/react-app/files/.prettierignore +6 -0
  271. package/templates/react-app/files/.prettierrc +9 -0
  272. package/templates/react-app/files/.yarnrc.yml +2 -0
  273. package/templates/react-app/files/Dockerfile +52 -0
  274. package/templates/react-app/files/_gitignore +33 -0
  275. package/templates/react-app/files/build.sh +53 -0
  276. package/templates/react-app/files/docker/nginx.conf +134 -0
  277. package/templates/react-app/files/eslint.config.mjs +75 -0
  278. package/templates/react-app/files/index.html +62 -0
  279. package/templates/react-app/files/openapi-ts.config.ts +22 -0
  280. package/templates/react-app/files/package.json +80 -0
  281. package/templates/react-app/files/src/app/nav.tsx +32 -0
  282. package/templates/react-app/files/src/app/providers.tsx +39 -0
  283. package/templates/react-app/files/src/app/router.tsx +142 -0
  284. package/templates/react-app/files/src/app/store.ts +20 -0
  285. package/templates/react-app/files/src/features/auth/components/auth-card.tsx +23 -0
  286. package/templates/react-app/files/src/features/auth/components/checkbox-field.tsx +32 -0
  287. package/templates/react-app/files/src/features/auth/components/google-button.tsx +41 -0
  288. package/templates/react-app/files/src/features/auth/components/or-divider.tsx +9 -0
  289. package/templates/react-app/files/src/features/auth/components/password-field.tsx +70 -0
  290. package/templates/react-app/files/src/features/auth/components/submit-button.tsx +16 -0
  291. package/templates/react-app/files/src/features/auth/lib/auth-actions.ts +68 -0
  292. package/templates/react-app/files/src/features/auth/lib/password-strength.ts +20 -0
  293. package/templates/react-app/files/src/features/auth/lib/redirect.ts +6 -0
  294. package/templates/react-app/files/src/features/auth/lib/schemas.ts +37 -0
  295. package/templates/react-app/files/src/features/seo/routes.ts +65 -0
  296. package/templates/react-app/files/src/lib/env.ts +28 -0
  297. package/templates/react-app/files/src/lib/paths.ts +58 -0
  298. package/templates/react-app/files/src/main.tsx +19 -0
  299. package/templates/react-app/files/src/routes/admin/home.tsx +24 -0
  300. package/templates/react-app/files/src/routes/auth/forgot-password.tsx +93 -0
  301. package/templates/react-app/files/src/routes/auth/login.tsx +87 -0
  302. package/templates/react-app/files/src/routes/auth/reset-password.tsx +83 -0
  303. package/templates/react-app/files/src/routes/auth/signup.tsx +117 -0
  304. package/templates/react-app/files/src/routes/auth/verified.tsx +31 -0
  305. package/templates/react-app/files/src/routes/auth/verify-email.tsx +97 -0
  306. package/templates/react-app/files/src/routes/home.tsx +17 -0
  307. package/templates/react-app/files/src/routes/legal/privacy.tsx +26 -0
  308. package/templates/react-app/files/src/routes/legal/terms.tsx +28 -0
  309. package/templates/react-app/files/src/routes/marketing/home.tsx +18 -0
  310. package/templates/react-app/files/src/routes/settings.tsx +10 -0
  311. package/templates/react-app/files/src/shared/api/base-api.ts +24 -0
  312. package/templates/react-app/files/src/shared/api/base-query.ts +29 -0
  313. package/templates/react-app/files/src/shared/api/config.ts +21 -0
  314. package/templates/react-app/files/src/shared/api/index.ts +15 -0
  315. package/templates/react-app/files/src/shared/api/realtime-events.ts +34 -0
  316. package/templates/react-app/files/src/shared/api/socket.ts +41 -0
  317. package/templates/react-app/files/src/shared/api/store.ts +39 -0
  318. package/templates/react-app/files/src/shared/api/upload-query.ts +115 -0
  319. package/templates/react-app/files/src/shared/auth/client.ts +27 -0
  320. package/templates/react-app/files/src/shared/auth/config.ts +28 -0
  321. package/templates/react-app/files/src/shared/auth/guards.tsx +75 -0
  322. package/templates/react-app/files/src/shared/auth/hooks.ts +13 -0
  323. package/templates/react-app/files/src/shared/auth/index.ts +20 -0
  324. package/templates/react-app/files/src/shared/auth/logout.ts +23 -0
  325. package/templates/react-app/files/src/shared/auth/provider.tsx +52 -0
  326. package/templates/react-app/files/src/shared/auth/redirect.ts +65 -0
  327. package/templates/react-app/files/src/shared/auth/slice.ts +54 -0
  328. package/templates/react-app/files/src/shared/hooks/index.ts +6 -0
  329. package/templates/react-app/files/src/shared/layouts/app-shell/app-shell.tsx +225 -0
  330. package/templates/react-app/files/src/shared/layouts/app-shell/user-menu.tsx +74 -0
  331. package/templates/react-app/files/src/shared/layouts/auth-layout.tsx +137 -0
  332. package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +38 -0
  333. package/templates/react-app/files/src/shared/layouts/index.ts +23 -0
  334. package/templates/react-app/files/src/shared/layouts/marketing/marketing-layout.tsx +29 -0
  335. package/templates/react-app/files/src/shared/layouts/marketing/section-heading.tsx +38 -0
  336. package/templates/react-app/files/src/shared/layouts/marketing/section.tsx +30 -0
  337. package/templates/react-app/files/src/shared/layouts/states/app-shell-skeleton.tsx +47 -0
  338. package/templates/react-app/files/src/shared/layouts/states/empty.tsx +22 -0
  339. package/templates/react-app/files/src/shared/layouts/states/forbidden.tsx +14 -0
  340. package/templates/react-app/files/src/shared/layouts/states/loading.tsx +11 -0
  341. package/templates/react-app/files/src/shared/layouts/states/not-found.tsx +21 -0
  342. package/templates/react-app/files/src/shared/layouts/states/page-skeleton.tsx +39 -0
  343. package/templates/react-app/files/src/shared/layouts/states/route-fallback.tsx +26 -0
  344. package/templates/react-app/files/src/shared/layouts/states/state-screen.tsx +22 -0
  345. package/templates/react-app/files/src/shared/types/README.md +28 -0
  346. package/templates/react-app/files/src/shared/types/api/index.ts +9 -0
  347. package/templates/react-app/files/src/shared/types/api/zod.gen.ts +5 -0
  348. package/templates/react-app/files/src/shared/types/index.ts +4 -0
  349. package/templates/react-app/files/src/shared/ui/components/brand/logo.tsx +51 -0
  350. package/templates/react-app/files/src/shared/ui/components/brand/social-icons.tsx +37 -0
  351. package/templates/react-app/files/src/shared/ui/components/form/form.tsx +120 -0
  352. package/templates/react-app/files/src/shared/ui/components/ui/alert.tsx +25 -0
  353. package/templates/react-app/files/src/shared/ui/components/ui/button.tsx +43 -0
  354. package/templates/react-app/files/src/shared/ui/components/ui/calendar.tsx +78 -0
  355. package/templates/react-app/files/src/shared/ui/components/ui/card.tsx +44 -0
  356. package/templates/react-app/files/src/shared/ui/components/ui/checkbox.tsx +16 -0
  357. package/templates/react-app/files/src/shared/ui/components/ui/date-picker.tsx +76 -0
  358. package/templates/react-app/files/src/shared/ui/components/ui/input.tsx +22 -0
  359. package/templates/react-app/files/src/shared/ui/components/ui/label.tsx +19 -0
  360. package/templates/react-app/files/src/shared/ui/components/ui/modal.tsx +94 -0
  361. package/templates/react-app/files/src/shared/ui/components/ui/popover.tsx +31 -0
  362. package/templates/react-app/files/src/shared/ui/components/ui/progress.tsx +33 -0
  363. package/templates/react-app/files/src/shared/ui/components/ui/search-input.tsx +69 -0
  364. package/templates/react-app/files/src/shared/ui/components/ui/select.tsx +80 -0
  365. package/templates/react-app/files/src/shared/ui/components/ui/skeleton.tsx +12 -0
  366. package/templates/react-app/files/src/shared/ui/components/ui/sonner.tsx +23 -0
  367. package/templates/react-app/files/src/shared/ui/components/ui/spinner.tsx +8 -0
  368. package/templates/react-app/files/src/shared/ui/components/ui/textarea.tsx +22 -0
  369. package/templates/react-app/files/src/shared/ui/index.ts +45 -0
  370. package/templates/react-app/files/src/shared/ui/lib/utils.ts +7 -0
  371. package/templates/react-app/files/src/shared/ui/lib/zod-setup.ts +28 -0
  372. package/templates/react-app/files/src/shared/ui/meta/brand.ts +18 -0
  373. package/templates/react-app/files/src/shared/ui/meta/meta-context.ts +30 -0
  374. package/templates/react-app/files/src/shared/ui/meta/meta-provider.tsx +24 -0
  375. package/templates/react-app/files/src/shared/ui/meta/page-meta.tsx +69 -0
  376. package/templates/react-app/files/src/shared/ui/meta/use-meta-config.ts +11 -0
  377. package/templates/react-app/files/src/shared/ui/styles/globals.css +285 -0
  378. package/templates/react-app/files/src/shared/ui/styles/theme.css +48 -0
  379. package/templates/react-app/files/src/shared/ui/theme/init-script.ts +23 -0
  380. package/templates/react-app/files/src/shared/ui/theme/theme-colors.ts +14 -0
  381. package/templates/react-app/files/src/shared/ui/theme/theme-context.ts +60 -0
  382. package/templates/react-app/files/src/shared/ui/theme/theme-provider.tsx +101 -0
  383. package/templates/react-app/files/src/shared/ui/theme/theme-toggle.tsx +40 -0
  384. package/templates/react-app/files/src/shared/ui/theme/use-theme.ts +11 -0
  385. package/templates/react-app/files/src/vite-env.d.ts +13 -0
  386. package/templates/react-app/files/tsconfig.json +26 -0
  387. package/templates/react-app/files/tsconfig.node.json +25 -0
  388. package/templates/react-app/files/vite.config.ts +344 -0
  389. package/templates/react-app/overrides/src/routes/auth/forgot-password.tsx +93 -0
  390. package/templates/react-app/overrides/src/routes/auth/login.tsx +87 -0
  391. package/templates/react-app/overrides/src/routes/auth/signup.tsx +117 -0
  392. package/templates/react-app/overrides/src/routes/auth/verified.tsx +31 -0
  393. package/templates/react-app/overrides/src/routes/auth/verify-email.tsx +97 -0
  394. package/templates/react-app/overrides/src/shared/auth/config.ts +28 -0
  395. package/templates/react-app/overrides/src/shared/auth/guards.tsx +75 -0
  396. package/templates/react-app/overrides/src/shared/auth/index.ts +20 -0
  397. package/templates/react-app/overrides/src/shared/auth/redirect.ts +65 -0
  398. package/templates/react-app/overrides/src/shared/hooks/index.ts +6 -0
  399. package/templates/react-app/overrides/src/shared/types/README.md +28 -0
  400. package/templates/react-app/template.json +222 -0
  401. package/templates/react-monorepo/files/.dockerignore +19 -0
  402. package/templates/react-monorepo/files/.editorconfig +12 -0
  403. package/templates/react-monorepo/files/.forgejo/workflows/build-and-push.yml +98 -0
  404. package/templates/react-monorepo/files/.forgejo/workflows/ci.yml +35 -0
  405. package/templates/react-monorepo/files/.husky/pre-commit +1 -0
  406. package/templates/react-monorepo/files/.nvmrc +1 -0
  407. package/templates/react-monorepo/files/.prettierignore +6 -0
  408. package/templates/react-monorepo/files/.prettierrc +9 -0
  409. package/templates/react-monorepo/files/.yarnrc.yml +2 -0
  410. package/templates/react-monorepo/files/Dockerfile +90 -0
  411. package/templates/react-monorepo/files/_gitignore +35 -0
  412. package/templates/react-monorepo/files/apps/admin/.bin/secrets.js +57 -0
  413. package/templates/react-monorepo/files/apps/admin/.env.example +16 -0
  414. package/templates/react-monorepo/files/apps/admin/eslint.config.mjs +3 -0
  415. package/templates/react-monorepo/files/apps/admin/index.html +57 -0
  416. package/templates/react-monorepo/files/apps/admin/package.json +33 -0
  417. package/templates/react-monorepo/files/apps/admin/src/App.tsx +3 -0
  418. package/templates/react-monorepo/files/apps/admin/src/app/nav.tsx +14 -0
  419. package/templates/react-monorepo/files/apps/admin/src/app/providers.tsx +42 -0
  420. package/templates/react-monorepo/files/apps/admin/src/app/router.tsx +50 -0
  421. package/templates/react-monorepo/files/apps/admin/src/app/store.ts +15 -0
  422. package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +12 -0
  423. package/templates/react-monorepo/files/apps/admin/src/main.tsx +14 -0
  424. package/templates/react-monorepo/files/apps/admin/src/routes/home.tsx +17 -0
  425. package/templates/react-monorepo/files/apps/admin/src/routes/settings.tsx +10 -0
  426. package/templates/react-monorepo/files/apps/admin/src/vite-env.d.ts +14 -0
  427. package/templates/react-monorepo/files/apps/admin/tsconfig.json +10 -0
  428. package/templates/react-monorepo/files/apps/admin/vite.config.ts +13 -0
  429. package/templates/react-monorepo/files/apps/auth/.bin/secrets.js +57 -0
  430. package/templates/react-monorepo/files/apps/auth/.env.example +16 -0
  431. package/templates/react-monorepo/files/apps/auth/eslint.config.mjs +3 -0
  432. package/templates/react-monorepo/files/apps/auth/index.html +57 -0
  433. package/templates/react-monorepo/files/apps/auth/package.json +32 -0
  434. package/templates/react-monorepo/files/apps/auth/src/App.tsx +3 -0
  435. package/templates/react-monorepo/files/apps/auth/src/app/providers.tsx +42 -0
  436. package/templates/react-monorepo/files/apps/auth/src/app/router.tsx +19 -0
  437. package/templates/react-monorepo/files/apps/auth/src/app/store.ts +15 -0
  438. package/templates/react-monorepo/files/apps/auth/src/components/auth-card.tsx +23 -0
  439. package/templates/react-monorepo/files/apps/auth/src/components/checkbox-field.tsx +32 -0
  440. package/templates/react-monorepo/files/apps/auth/src/components/google-button.tsx +41 -0
  441. package/templates/react-monorepo/files/apps/auth/src/components/or-divider.tsx +9 -0
  442. package/templates/react-monorepo/files/apps/auth/src/components/password-field.tsx +70 -0
  443. package/templates/react-monorepo/files/apps/auth/src/components/submit-button.tsx +16 -0
  444. package/templates/react-monorepo/files/apps/auth/src/lib/auth-actions.ts +68 -0
  445. package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +8 -0
  446. package/templates/react-monorepo/files/apps/auth/src/lib/password-strength.ts +20 -0
  447. package/templates/react-monorepo/files/apps/auth/src/lib/redirect.ts +6 -0
  448. package/templates/react-monorepo/files/apps/auth/src/lib/schemas.ts +37 -0
  449. package/templates/react-monorepo/files/apps/auth/src/main.tsx +14 -0
  450. package/templates/react-monorepo/files/apps/auth/src/routes/forgot-password.tsx +91 -0
  451. package/templates/react-monorepo/files/apps/auth/src/routes/login.tsx +82 -0
  452. package/templates/react-monorepo/files/apps/auth/src/routes/reset-password.tsx +83 -0
  453. package/templates/react-monorepo/files/apps/auth/src/routes/signup.tsx +117 -0
  454. package/templates/react-monorepo/files/apps/auth/src/routes/verified.tsx +30 -0
  455. package/templates/react-monorepo/files/apps/auth/src/routes/verify-email.tsx +96 -0
  456. package/templates/react-monorepo/files/apps/auth/src/vite-env.d.ts +14 -0
  457. package/templates/react-monorepo/files/apps/auth/tsconfig.json +10 -0
  458. package/templates/react-monorepo/files/apps/auth/vite.config.ts +13 -0
  459. package/templates/react-monorepo/files/apps/client/.bin/secrets.js +57 -0
  460. package/templates/react-monorepo/files/apps/client/.env.example +16 -0
  461. package/templates/react-monorepo/files/apps/client/eslint.config.mjs +3 -0
  462. package/templates/react-monorepo/files/apps/client/index.html +57 -0
  463. package/templates/react-monorepo/files/apps/client/package.json +35 -0
  464. package/templates/react-monorepo/files/apps/client/src/App.tsx +3 -0
  465. package/templates/react-monorepo/files/apps/client/src/app/nav.tsx +14 -0
  466. package/templates/react-monorepo/files/apps/client/src/app/providers.tsx +42 -0
  467. package/templates/react-monorepo/files/apps/client/src/app/router.tsx +50 -0
  468. package/templates/react-monorepo/files/apps/client/src/app/store.ts +15 -0
  469. package/templates/react-monorepo/files/apps/client/src/lib/env.ts +12 -0
  470. package/templates/react-monorepo/files/apps/client/src/main.tsx +19 -0
  471. package/templates/react-monorepo/files/apps/client/src/routes/home.tsx +17 -0
  472. package/templates/react-monorepo/files/apps/client/src/routes/settings.tsx +10 -0
  473. package/templates/react-monorepo/files/apps/client/src/vite-env.d.ts +15 -0
  474. package/templates/react-monorepo/files/apps/client/tsconfig.json +10 -0
  475. package/templates/react-monorepo/files/apps/client/vite.config.ts +120 -0
  476. package/templates/react-monorepo/files/apps/landing/.bin/secrets.js +57 -0
  477. package/templates/react-monorepo/files/apps/landing/.env.example +20 -0
  478. package/templates/react-monorepo/files/apps/landing/eslint.config.mjs +3 -0
  479. package/templates/react-monorepo/files/apps/landing/index.html +51 -0
  480. package/templates/react-monorepo/files/apps/landing/package.json +33 -0
  481. package/templates/react-monorepo/files/apps/landing/src/App.tsx +3 -0
  482. package/templates/react-monorepo/files/apps/landing/src/app/providers.tsx +42 -0
  483. package/templates/react-monorepo/files/apps/landing/src/app/router.tsx +21 -0
  484. package/templates/react-monorepo/files/apps/landing/src/app/store.ts +18 -0
  485. package/templates/react-monorepo/files/apps/landing/src/features/seo/routes.ts +65 -0
  486. package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
  487. package/templates/react-monorepo/files/apps/landing/src/main.tsx +14 -0
  488. package/templates/react-monorepo/files/apps/landing/src/routes/home.tsx +18 -0
  489. package/templates/react-monorepo/files/apps/landing/src/vite-env.d.ts +15 -0
  490. package/templates/react-monorepo/files/apps/landing/tsconfig.json +10 -0
  491. package/templates/react-monorepo/files/apps/landing/vite.config.ts +169 -0
  492. package/templates/react-monorepo/files/build.sh +61 -0
  493. package/templates/react-monorepo/files/docker/nginx.conf +136 -0
  494. package/templates/react-monorepo/files/package.json +37 -0
  495. package/templates/react-monorepo/files/packages/.gitkeep +0 -0
  496. package/templates/react-monorepo/files/packages/api/eslint.config.mjs +3 -0
  497. package/templates/react-monorepo/files/packages/api/package.json +30 -0
  498. package/templates/react-monorepo/files/packages/api/src/base-api.ts +24 -0
  499. package/templates/react-monorepo/files/packages/api/src/base-query.ts +29 -0
  500. package/templates/react-monorepo/files/packages/api/src/config.ts +21 -0
  501. package/templates/react-monorepo/files/packages/api/src/index.ts +15 -0
  502. package/templates/react-monorepo/files/packages/api/src/realtime-events.ts +34 -0
  503. package/templates/react-monorepo/files/packages/api/src/socket.ts +41 -0
  504. package/templates/react-monorepo/files/packages/api/src/store.ts +39 -0
  505. package/templates/react-monorepo/files/packages/api/src/upload-query.ts +115 -0
  506. package/templates/react-monorepo/files/packages/api/tsconfig.json +4 -0
  507. package/templates/react-monorepo/files/packages/auth/eslint.config.mjs +3 -0
  508. package/templates/react-monorepo/files/packages/auth/package.json +33 -0
  509. package/templates/react-monorepo/files/packages/auth/src/client.ts +27 -0
  510. package/templates/react-monorepo/files/packages/auth/src/config.ts +22 -0
  511. package/templates/react-monorepo/files/packages/auth/src/guards.tsx +61 -0
  512. package/templates/react-monorepo/files/packages/auth/src/hooks.ts +13 -0
  513. package/templates/react-monorepo/files/packages/auth/src/index.ts +20 -0
  514. package/templates/react-monorepo/files/packages/auth/src/logout.ts +23 -0
  515. package/templates/react-monorepo/files/packages/auth/src/provider.tsx +52 -0
  516. package/templates/react-monorepo/files/packages/auth/src/redirect.ts +37 -0
  517. package/templates/react-monorepo/files/packages/auth/src/slice.ts +54 -0
  518. package/templates/react-monorepo/files/packages/auth/tsconfig.json +4 -0
  519. package/templates/react-monorepo/files/packages/config/README.md +47 -0
  520. package/templates/react-monorepo/files/packages/config/eslint/base.mjs +83 -0
  521. package/templates/react-monorepo/files/packages/config/package.json +37 -0
  522. package/templates/react-monorepo/files/packages/config/tailwind/theme.css +48 -0
  523. package/templates/react-monorepo/files/packages/config/tsconfig/base.json +22 -0
  524. package/templates/react-monorepo/files/packages/config/tsconfig/node.json +9 -0
  525. package/templates/react-monorepo/files/packages/config/tsconfig/react.json +9 -0
  526. package/templates/react-monorepo/files/packages/config/vite/index.d.ts +18 -0
  527. package/templates/react-monorepo/files/packages/config/vite/index.mjs +129 -0
  528. package/templates/react-monorepo/files/packages/hooks/eslint.config.mjs +3 -0
  529. package/templates/react-monorepo/files/packages/hooks/package.json +16 -0
  530. package/templates/react-monorepo/files/packages/hooks/src/index.ts +3 -0
  531. package/templates/react-monorepo/files/packages/hooks/tsconfig.json +4 -0
  532. package/templates/react-monorepo/files/packages/layouts/eslint.config.mjs +3 -0
  533. package/templates/react-monorepo/files/packages/layouts/package.json +30 -0
  534. package/templates/react-monorepo/files/packages/layouts/src/app-shell/app-shell.tsx +225 -0
  535. package/templates/react-monorepo/files/packages/layouts/src/app-shell/user-menu.tsx +74 -0
  536. package/templates/react-monorepo/files/packages/layouts/src/auth-layout.tsx +137 -0
  537. package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +38 -0
  538. package/templates/react-monorepo/files/packages/layouts/src/index.ts +23 -0
  539. package/templates/react-monorepo/files/packages/layouts/src/marketing/marketing-layout.tsx +29 -0
  540. package/templates/react-monorepo/files/packages/layouts/src/marketing/section-heading.tsx +38 -0
  541. package/templates/react-monorepo/files/packages/layouts/src/marketing/section.tsx +30 -0
  542. package/templates/react-monorepo/files/packages/layouts/src/states/app-shell-skeleton.tsx +47 -0
  543. package/templates/react-monorepo/files/packages/layouts/src/states/empty.tsx +22 -0
  544. package/templates/react-monorepo/files/packages/layouts/src/states/forbidden.tsx +14 -0
  545. package/templates/react-monorepo/files/packages/layouts/src/states/loading.tsx +11 -0
  546. package/templates/react-monorepo/files/packages/layouts/src/states/not-found.tsx +21 -0
  547. package/templates/react-monorepo/files/packages/layouts/src/states/page-skeleton.tsx +39 -0
  548. package/templates/react-monorepo/files/packages/layouts/src/states/route-fallback.tsx +26 -0
  549. package/templates/react-monorepo/files/packages/layouts/src/states/state-screen.tsx +22 -0
  550. package/templates/react-monorepo/files/packages/layouts/tsconfig.json +4 -0
  551. package/templates/react-monorepo/files/packages/types/.bin/openapi.js +32 -0
  552. package/templates/react-monorepo/files/packages/types/.bin/secrets.js +57 -0
  553. package/templates/react-monorepo/files/packages/types/README.md +28 -0
  554. package/templates/react-monorepo/files/packages/types/eslint.config.mjs +3 -0
  555. package/templates/react-monorepo/files/packages/types/openapi-ts.config.ts +22 -0
  556. package/templates/react-monorepo/files/packages/types/package.json +29 -0
  557. package/templates/react-monorepo/files/packages/types/src/index.ts +4 -0
  558. package/templates/react-monorepo/files/packages/types/src/types/api/index.ts +9 -0
  559. package/templates/react-monorepo/files/packages/types/src/types/api/zod.gen.ts +5 -0
  560. package/templates/react-monorepo/files/packages/types/tsconfig.json +7 -0
  561. package/templates/react-monorepo/files/packages/ui/components.json +21 -0
  562. package/templates/react-monorepo/files/packages/ui/eslint.config.mjs +13 -0
  563. package/templates/react-monorepo/files/packages/ui/package.json +44 -0
  564. package/templates/react-monorepo/files/packages/ui/src/components/brand/logo.tsx +51 -0
  565. package/templates/react-monorepo/files/packages/ui/src/components/brand/social-icons.tsx +37 -0
  566. package/templates/react-monorepo/files/packages/ui/src/components/form/form.tsx +120 -0
  567. package/templates/react-monorepo/files/packages/ui/src/components/ui/alert.tsx +25 -0
  568. package/templates/react-monorepo/files/packages/ui/src/components/ui/button.tsx +43 -0
  569. package/templates/react-monorepo/files/packages/ui/src/components/ui/calendar.tsx +78 -0
  570. package/templates/react-monorepo/files/packages/ui/src/components/ui/card.tsx +44 -0
  571. package/templates/react-monorepo/files/packages/ui/src/components/ui/checkbox.tsx +16 -0
  572. package/templates/react-monorepo/files/packages/ui/src/components/ui/date-picker.tsx +76 -0
  573. package/templates/react-monorepo/files/packages/ui/src/components/ui/input.tsx +22 -0
  574. package/templates/react-monorepo/files/packages/ui/src/components/ui/label.tsx +19 -0
  575. package/templates/react-monorepo/files/packages/ui/src/components/ui/modal.tsx +94 -0
  576. package/templates/react-monorepo/files/packages/ui/src/components/ui/popover.tsx +31 -0
  577. package/templates/react-monorepo/files/packages/ui/src/components/ui/progress.tsx +33 -0
  578. package/templates/react-monorepo/files/packages/ui/src/components/ui/search-input.tsx +69 -0
  579. package/templates/react-monorepo/files/packages/ui/src/components/ui/select.tsx +80 -0
  580. package/templates/react-monorepo/files/packages/ui/src/components/ui/skeleton.tsx +12 -0
  581. package/templates/react-monorepo/files/packages/ui/src/components/ui/sonner.tsx +23 -0
  582. package/templates/react-monorepo/files/packages/ui/src/components/ui/spinner.tsx +8 -0
  583. package/templates/react-monorepo/files/packages/ui/src/components/ui/textarea.tsx +22 -0
  584. package/templates/react-monorepo/files/packages/ui/src/index.ts +45 -0
  585. package/templates/react-monorepo/files/packages/ui/src/lib/utils.ts +7 -0
  586. package/templates/react-monorepo/files/packages/ui/src/lib/zod-setup.ts +28 -0
  587. package/templates/react-monorepo/files/packages/ui/src/meta/brand.ts +18 -0
  588. package/templates/react-monorepo/files/packages/ui/src/meta/meta-context.ts +30 -0
  589. package/templates/react-monorepo/files/packages/ui/src/meta/meta-provider.tsx +24 -0
  590. package/templates/react-monorepo/files/packages/ui/src/meta/page-meta.tsx +69 -0
  591. package/templates/react-monorepo/files/packages/ui/src/meta/use-meta-config.ts +11 -0
  592. package/templates/react-monorepo/files/packages/ui/src/styles/globals.css +285 -0
  593. package/templates/react-monorepo/files/packages/ui/src/theme/init-script.ts +23 -0
  594. package/templates/react-monorepo/files/packages/ui/src/theme/theme-colors.ts +14 -0
  595. package/templates/react-monorepo/files/packages/ui/src/theme/theme-context.ts +60 -0
  596. package/templates/react-monorepo/files/packages/ui/src/theme/theme-provider.tsx +101 -0
  597. package/templates/react-monorepo/files/packages/ui/src/theme/theme-toggle.tsx +40 -0
  598. package/templates/react-monorepo/files/packages/ui/src/theme/use-theme.ts +11 -0
  599. package/templates/react-monorepo/files/packages/ui/tsconfig.json +9 -0
  600. package/templates/react-monorepo/files/turbo.json +24 -0
  601. package/templates/react-monorepo/template.json +230 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Socket.io path — kept distinct from the REST surface so a reverse proxy can
3
+ * route WebSocket upgrades independently. The frontend must connect with the
4
+ * same `path`.
5
+ */
6
+ export const SOCKET_PATH = '/realtime';
7
+
8
+ /**
9
+ * Server → client event names. Keeping them centralized means the gateway, the
10
+ * publisher service and the frontend share a single source of truth instead of
11
+ * trading raw strings.
12
+ */
13
+ export const ServerEvent = {
14
+ /** Presence roster changed (a user came online / went offline). */
15
+ PRESENCE_UPDATE: 'presence:update',
16
+ /** Someone started/stopped typing in a room. */
17
+ TYPING: 'activity:typing',
18
+ /** Someone is viewing a resource. */
19
+ VIEWING: 'activity:viewing',
20
+ /** A user-directed notification. */
21
+ NOTIFICATION: 'notification',
22
+ /** Something in a channel changed; payload is app-defined. */
23
+ CHANNEL_UPDATE: 'channel:update'
24
+ } as const;
25
+
26
+ /**
27
+ * Client → server event names.
28
+ */
29
+ export const ClientEvent = {
30
+ JOIN_ROOM: 'room:join',
31
+ LEAVE_ROOM: 'room:leave',
32
+ TYPING: 'activity:typing',
33
+ VIEWING: 'activity:viewing'
34
+ } as const;
35
+
36
+ /** Every socket of a single user joins this room, so we can target the user. */
37
+ export const userRoom = (userId: string) => `user:${userId}`;
38
+
39
+ /** Firehose room — clients here see every broadcast on the default channel. */
40
+ export const FEED_ROOM = 'feed';
41
+
42
+ /** Room scoped to one resource; comments, typing and viewing land here. */
43
+ export const channelRoom = (channelId: string) => `channel:${channelId}`;
44
+
45
+ /** cuid-shaped ids, as generated by Prisma's `@default(cuid())`. */
46
+ const CHANNEL_ROOM_PATTERN = /^channel:[A-Za-z0-9_-]{1,64}$/;
47
+
48
+ /**
49
+ * Rooms a client may join via the generic `room:join` message.
50
+ *
51
+ * This is an allowlist on purpose. Rooms are plain strings, so an open
52
+ * `socket.join(body.room)` would let any authenticated user join `user:<id>`
53
+ * and receive another user's private notifications (see
54
+ * `RealtimeService.notifyUser`). That is reachable only through this door, so
55
+ * widen the allowlist deliberately — never by passing the client's string
56
+ * straight to `socket.join`.
57
+ */
58
+ export const isJoinableRoom = (room: string): boolean =>
59
+ room === FEED_ROOM || CHANNEL_ROOM_PATTERN.test(room);
60
+
61
+ /** Redis key holding the set of currently-connected user ids (presence). */
62
+ export const PRESENCE_KEY = 'realtime:presence:online';
63
+ /**
64
+ * Per-user connection counter. A user may have several tabs/devices; they only
65
+ * count as "offline" once every socket has disconnected.
66
+ */
67
+ export const presenceCountKey = (userId: string) =>
68
+ `realtime:presence:count:${userId}`;
@@ -0,0 +1 @@
1
+ export * from './ws-permissions.decorator';
@@ -0,0 +1,14 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+ import { Permission } from 'lib/access';
3
+
4
+ export const WS_PERMISSIONS_KEY = 'ws:permissions';
5
+
6
+ /**
7
+ * Require a permission on a `@SubscribeMessage` handler, mirroring what
8
+ * `@UserHasPermission` does for HTTP routes. Enforced by `WsPermissionsGuard`.
9
+ *
10
+ * Websocket handlers never pass through the global HTTP `AuthGuard`, so without
11
+ * this they are gated on identity alone.
12
+ */
13
+ export const WsPermissions = (permission: Permission) =>
14
+ SetMetadata(WS_PERMISSIONS_KEY, permission);
@@ -0,0 +1,187 @@
1
+ import { UseGuards } from '@nestjs/common';
2
+ import {
3
+ ConnectedSocket,
4
+ MessageBody,
5
+ OnGatewayConnection,
6
+ OnGatewayDisconnect,
7
+ OnGatewayInit,
8
+ SubscribeMessage,
9
+ WebSocketGateway,
10
+ WebSocketServer,
11
+ WsException
12
+ } from '@nestjs/websockets';
13
+ import { Server, Socket } from 'socket.io';
14
+ import { CustomAuthService, LoggerService } from 'common/services';
15
+ import { PresenceService } from '../services/presence.service';
16
+ import { RealtimeService } from '../services/realtime.service';
17
+ import { WsAuthGuard, WsPermissionsGuard } from '../guards';
18
+ import { getSessionUserFromSocket } from '../misc/session-from-socket';
19
+ import {
20
+ ClientEvent,
21
+ ServerEvent,
22
+ isJoinableRoom,
23
+ userRoom
24
+ } from '../constants';
25
+
26
+ /**
27
+ * Central Socket.io gateway.
28
+ *
29
+ * Responsibilities (PRD §2.2 / §3.1):
30
+ * - Authenticate every socket against the better-auth session on connect.
31
+ * - Maintain online-user presence across replicas (via {@link PresenceService}).
32
+ * - Let clients subscribe to live market rooms for price tickers.
33
+ * - Relay lightweight activity signals (typing / viewing) within a room.
34
+ *
35
+ * The `cors` / `path` for the server are configured on the Redis adapter
36
+ * (`RedisIoAdapter`), so the decorator stays option-free.
37
+ */
38
+ @WebSocketGateway()
39
+ // Applied at class level so a newly added handler is guarded by default rather
40
+ // than by remembering to decorate it. `handleConnection` is not a message
41
+ // handler and is unaffected — it does its own session check.
42
+ /**
43
+ * Guards are applied at the **class** level, so every `@SubscribeMessage`
44
+ * handler is covered by default — Nest concatenates class- then method-level
45
+ * guard metadata. `WsAuthGuard` establishes identity; `WsPermissionsGuard`
46
+ * enforces `@WsPermissions({ <resource>: ['<action>'] })` from
47
+ * `realtime/decorators`, which is the only authorization path websockets get
48
+ * (they never traverse the HTTP `AuthGuard`).
49
+ */
50
+ @UseGuards(WsAuthGuard, WsPermissionsGuard)
51
+ export class EventsGateway
52
+ implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
53
+ {
54
+ @WebSocketServer() private server!: Server;
55
+
56
+ constructor(
57
+ private readonly auth: CustomAuthService,
58
+ private readonly presence: PresenceService,
59
+ private readonly realtime: RealtimeService,
60
+ private readonly logger: LoggerService
61
+ ) {}
62
+
63
+ afterInit(server: Server) {
64
+ // Hand the live server to the publisher so other modules can emit.
65
+ this.realtime.bindServer(server);
66
+ this.logger.debug('Realtime gateway initialized.');
67
+ }
68
+
69
+ async handleConnection(socket: Socket) {
70
+ const user = await getSessionUserFromSocket(socket, this.auth);
71
+ if (!user) {
72
+ // No valid session — refuse the connection.
73
+ socket.emit('error', { message: 'Unauthorized' });
74
+ socket.disconnect(true);
75
+ return;
76
+ }
77
+
78
+ socket.data.user = user;
79
+ // Every socket of a user joins their personal room for direct messaging.
80
+ await socket.join(userRoom(user.id));
81
+
82
+ const cameOnline = await this.presence.connect(user.id);
83
+ if (cameOnline) {
84
+ this.server.emit(ServerEvent.PRESENCE_UPDATE, {
85
+ userId: user.id,
86
+ status: 'online'
87
+ });
88
+ }
89
+
90
+ this.logger.debug(
91
+ { userId: user.id, socketId: socket.id },
92
+ 'Socket connected.'
93
+ );
94
+ }
95
+
96
+ async handleDisconnect(socket: Socket) {
97
+ const user = socket.data.user;
98
+ if (!user) return;
99
+
100
+ const wentOffline = await this.presence.disconnect(user.id);
101
+ if (wentOffline) {
102
+ this.server.emit(ServerEvent.PRESENCE_UPDATE, {
103
+ userId: user.id,
104
+ status: 'offline'
105
+ });
106
+ }
107
+
108
+ this.logger.debug(
109
+ { userId: user.id, socketId: socket.id },
110
+ 'Socket disconnected.'
111
+ );
112
+ }
113
+
114
+ // --- Presence ----------------------------------------------------------
115
+
116
+ @SubscribeMessage('presence:list')
117
+ async listOnline() {
118
+ const userIds = await this.presence.online();
119
+ return { event: ServerEvent.PRESENCE_UPDATE, data: { online: userIds } };
120
+ }
121
+
122
+ // --- Rooms --------------------------------------------------------------
123
+
124
+ @SubscribeMessage(ClientEvent.JOIN_ROOM)
125
+ async joinRoom(
126
+ @ConnectedSocket() socket: Socket,
127
+ @MessageBody() body: { room: string }
128
+ ) {
129
+ // Only rooms on the allowlist are joinable this way. `user:<id>` is not —
130
+ // it would hand the caller another user's private notifications.
131
+ if (!isJoinableRoom(body.room)) {
132
+ throw new WsException('Forbidden');
133
+ }
134
+ await socket.join(body.room);
135
+ return { ok: true, room: body.room };
136
+ }
137
+
138
+ @SubscribeMessage(ClientEvent.LEAVE_ROOM)
139
+ async leaveRoom(
140
+ @ConnectedSocket() socket: Socket,
141
+ @MessageBody() body: { room: string }
142
+ ) {
143
+ await socket.leave(body.room);
144
+ return { ok: true };
145
+ }
146
+
147
+ // --- Activity signals (who is typing / viewing) ------------------------
148
+
149
+ @SubscribeMessage(ClientEvent.TYPING)
150
+ typing(
151
+ @ConnectedSocket() socket: Socket,
152
+ @MessageBody() body: { room: string; isTyping: boolean }
153
+ ) {
154
+ this.assertInRoom(socket, body.room);
155
+ // Relay to everyone else in the room — never echo back to the sender.
156
+ socket.to(body.room).emit(ServerEvent.TYPING, {
157
+ room: body.room,
158
+ userId: socket.data.user!.id,
159
+ name: socket.data.user!.name,
160
+ isTyping: body.isTyping
161
+ });
162
+ }
163
+
164
+ @SubscribeMessage(ClientEvent.VIEWING)
165
+ viewing(
166
+ @ConnectedSocket() socket: Socket,
167
+ @MessageBody() body: { room: string; viewing: boolean }
168
+ ) {
169
+ this.assertInRoom(socket, body.room);
170
+ socket.to(body.room).emit(ServerEvent.VIEWING, {
171
+ room: body.room,
172
+ userId: socket.data.user!.id,
173
+ viewing: body.viewing
174
+ });
175
+ }
176
+
177
+ /**
178
+ * A client may only signal into a room it actually occupies. Otherwise it
179
+ * could emit forged typing/viewing events into any room by name, including
180
+ * another user's `user:<id>` room.
181
+ */
182
+ private assertInRoom(socket: Socket, room: string) {
183
+ if (!socket.rooms.has(room)) {
184
+ throw new WsException('Not in room');
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ws-auth.guard';
2
+ export * from './ws-permissions.guard';
@@ -0,0 +1,20 @@
1
+ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
2
+ import { WsException } from '@nestjs/websockets';
3
+ import { Socket } from 'socket.io';
4
+
5
+ /**
6
+ * Guards `@SubscribeMessage` handlers: the socket must already carry an
7
+ * authenticated user (attached at connection time in `EventsGateway`). This is
8
+ * defense-in-depth — unauthenticated sockets are disconnected on connect, but
9
+ * message handlers should never trust that implicitly.
10
+ */
11
+ @Injectable()
12
+ export class WsAuthGuard implements CanActivate {
13
+ canActivate(context: ExecutionContext): boolean {
14
+ const socket = context.switchToWs().getClient<Socket>();
15
+ if (!socket.data.user) {
16
+ throw new WsException('Unauthorized');
17
+ }
18
+ return true;
19
+ }
20
+ }
@@ -0,0 +1,35 @@
1
+ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { WsException } from '@nestjs/websockets';
4
+ import { Socket } from 'socket.io';
5
+ import { Permission, hasPermission } from 'lib/access';
6
+ import { WS_PERMISSIONS_KEY } from '../decorators/ws-permissions.decorator';
7
+
8
+ /**
9
+ * Enforces `@WsPermissions(...)` on socket message handlers.
10
+ *
11
+ * Runs after `WsAuthGuard`, which establishes *who* the socket is; this decides
12
+ * *what* they may do. A handler with no `@WsPermissions` metadata is allowed —
13
+ * identity alone is the requirement there.
14
+ */
15
+ @Injectable()
16
+ export class WsPermissionsGuard implements CanActivate {
17
+ constructor(private readonly reflector: Reflector) {}
18
+
19
+ canActivate(context: ExecutionContext): boolean {
20
+ const required = this.reflector.getAllAndOverride<Permission | undefined>(
21
+ WS_PERMISSIONS_KEY,
22
+ [context.getHandler(), context.getClass()]
23
+ );
24
+ if (!required) return true;
25
+
26
+ const socket = context.switchToWs().getClient<Socket>();
27
+ const user = socket.data.user;
28
+ if (!user) throw new WsException('Unauthorized');
29
+
30
+ if (!hasPermission(user.role, required)) {
31
+ throw new WsException('Forbidden');
32
+ }
33
+ return true;
34
+ }
35
+ }
@@ -0,0 +1,54 @@
1
+ import { Socket } from 'socket.io';
2
+ import { CustomAuthService } from 'common/services';
3
+
4
+ /** The authenticated identity we stash on `socket.data` after the handshake. */
5
+ export interface SocketUser {
6
+ id: string;
7
+ name: string;
8
+ email: string;
9
+ role?: string | null;
10
+ }
11
+
12
+ declare module 'socket.io' {
13
+ interface SocketData {
14
+ user?: SocketUser;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Resolve a better-auth session from a socket handshake.
20
+ *
21
+ * The browser sends the same session cookie it uses for REST, so we forward the
22
+ * handshake headers to better-auth's `getSession`. Returns `null` when there is
23
+ * no valid session — callers decide whether to disconnect.
24
+ */
25
+ export const getSessionUserFromSocket = async (
26
+ socket: Socket,
27
+ auth: CustomAuthService
28
+ ): Promise<SocketUser | null> => {
29
+ // socket.io exposes handshake headers as a plain object; better-auth wants a
30
+ // Fetch `Headers`. The cookie is the only header it needs.
31
+ const headers = new Headers();
32
+ const cookie = socket.handshake.headers.cookie;
33
+ if (cookie) headers.set('cookie', cookie);
34
+
35
+ // Also accept a bearer token (handy for native/PWA clients that can't rely on
36
+ // cookies), mirroring better-auth's bearer support.
37
+ const auth_header =
38
+ socket.handshake.auth?.token ?? socket.handshake.headers.authorization;
39
+ if (auth_header) {
40
+ headers.set(
41
+ 'authorization',
42
+ auth_header.startsWith('Bearer ') ? auth_header : `Bearer ${auth_header}`
43
+ );
44
+ }
45
+
46
+ try {
47
+ const session = await auth.api.getSession({ headers });
48
+ if (!session?.user) return null;
49
+ const { id, name, email, role } = session.user;
50
+ return { id, name, email, role };
51
+ } catch {
52
+ return null;
53
+ }
54
+ };
@@ -0,0 +1,26 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { EventsGateway } from './gateways/events.gateway';
3
+ import { PresenceService } from './services/presence.service';
4
+ import { RealtimeService } from './services/realtime.service';
5
+ import { WsAuthGuard, WsPermissionsGuard } from './guards';
6
+
7
+ /**
8
+ * Realtime (Socket.io) layer. Marked `@Global` so cross-cutting publishers —
9
+ * queue processors, schedulers and REST services — can
10
+ * inject {@link RealtimeService} without importing this module everywhere.
11
+ *
12
+ * The Redis adapter for horizontal scaling is wired separately in `main.ts`
13
+ * (`RedisIoAdapter`) because it must be attached to the app before `listen()`.
14
+ */
15
+ @Global()
16
+ @Module({
17
+ providers: [
18
+ EventsGateway,
19
+ PresenceService,
20
+ RealtimeService,
21
+ WsAuthGuard,
22
+ WsPermissionsGuard
23
+ ],
24
+ exports: [RealtimeService, PresenceService]
25
+ })
26
+ export class RealtimeModule {}
@@ -0,0 +1,56 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { CacheService } from 'common/services';
3
+ import { PRESENCE_KEY, presenceCountKey } from '../constants';
4
+
5
+ /**
6
+ * Tracks which users are online across all backend replicas.
7
+ *
8
+ * A user can hold several sockets at once (multiple tabs / devices), so we keep
9
+ * a per-user connection counter in Redis and only flip presence when it crosses
10
+ * the 0↔1 boundary. State lives in Redis (not memory) so it is correct behind
11
+ * the horizontally-scaled Socket.io adapter.
12
+ */
13
+ @Injectable()
14
+ export class PresenceService {
15
+ constructor(private readonly cache: CacheService) {}
16
+
17
+ private get redis() {
18
+ return this.cache.db;
19
+ }
20
+
21
+ /**
22
+ * Record a new socket for a user. Returns `true` if this was their first
23
+ * connection (i.e. they just came online), so the caller can broadcast it.
24
+ */
25
+ async connect(userId: string): Promise<boolean> {
26
+ const count = await this.redis.incr(presenceCountKey(userId));
27
+ if (count === 1) {
28
+ await this.redis.sadd(PRESENCE_KEY, userId);
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+
34
+ /**
35
+ * Record a socket closing for a user. Returns `true` if that was their last
36
+ * connection (i.e. they just went offline).
37
+ */
38
+ async disconnect(userId: string): Promise<boolean> {
39
+ const count = await this.redis.decr(presenceCountKey(userId));
40
+ if (count <= 0) {
41
+ await this.redis.del(presenceCountKey(userId));
42
+ await this.redis.srem(PRESENCE_KEY, userId);
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+
48
+ /** Ids of every currently-online user. */
49
+ async online(): Promise<string[]> {
50
+ return this.redis.smembers(PRESENCE_KEY);
51
+ }
52
+
53
+ async isOnline(userId: string): Promise<boolean> {
54
+ return (await this.redis.sismember(PRESENCE_KEY, userId)) === 1;
55
+ }
56
+ }
@@ -0,0 +1,72 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { Server } from 'socket.io';
3
+ import { LoggerService } from 'common/services';
4
+ import { FEED_ROOM, ServerEvent, channelRoom, userRoom } from '../constants';
5
+
6
+ /**
7
+ * The publish side of the realtime layer.
8
+ *
9
+ * Any module (queue processors, schedulers, REST services) can inject this to
10
+ * push events to connected clients without depending on the gateway — which
11
+ * would create a cycle and couple business logic to the transport. The gateway
12
+ * hands us the live Socket.io `Server` in its `afterInit`; before that (or in
13
+ * tests with no WS server) every emit is a safe no-op.
14
+ */
15
+ @Injectable()
16
+ export class RealtimeService {
17
+ private server?: Server;
18
+
19
+ constructor(private readonly logger: LoggerService) {}
20
+
21
+ /** Called once by the gateway when the Socket.io server is ready. */
22
+ bindServer(server: Server) {
23
+ this.server = server;
24
+ }
25
+
26
+ get isReady(): boolean {
27
+ return !!this.server;
28
+ }
29
+
30
+ /** Deliver a notification to every socket belonging to one user. */
31
+ notifyUser(userId: string, notification: unknown) {
32
+ this.emitToRoom(userRoom(userId), ServerEvent.NOTIFICATION, notification);
33
+ }
34
+
35
+ /** Announce something to everyone watching the global feed. */
36
+ emitToFeed(event: string, payload: unknown) {
37
+ this.emitToRoom(FEED_ROOM, event, payload);
38
+ }
39
+
40
+ /** Push an update to everyone in one channel's room. */
41
+ emitToChannel(channelId: string, payload: unknown) {
42
+ this.emitToRoom(
43
+ channelRoom(channelId),
44
+ ServerEvent.CHANNEL_UPDATE,
45
+ payload
46
+ );
47
+ }
48
+
49
+ /** Low-level: emit to a specific room. */
50
+ emitToRoom(room: string, event: string, payload: unknown) {
51
+ if (!this.server) {
52
+ this.logger.debug(
53
+ { room, event },
54
+ 'Realtime server not ready; dropping emit.'
55
+ );
56
+ return;
57
+ }
58
+ this.server.to(room).emit(event, payload);
59
+ }
60
+
61
+ /** Low-level: emit to every connected client. */
62
+ broadcast(event: string, payload: unknown) {
63
+ if (!this.server) {
64
+ this.logger.debug(
65
+ { event },
66
+ 'Realtime server not ready; dropping broadcast.'
67
+ );
68
+ return;
69
+ }
70
+ this.server.emit(event, payload);
71
+ }
72
+ }
@@ -0,0 +1,77 @@
1
+ import { TestBed, type Mocked } from '@suites/unit';
2
+ import { Entity } from '@test/factory/entity';
3
+ import { CurrentUserController } from '../controllers';
4
+ import { CurrentUserService } from '../services';
5
+ import { CurrentUserEntity, toCurrentUser } from '../entity';
6
+
7
+ describe('CurrentUserController', () => {
8
+ let controller: CurrentUserController;
9
+ let service: Mocked<CurrentUserService>;
10
+
11
+ beforeAll(async () => {
12
+ const { unit, unitRef } = await TestBed.solitary(
13
+ CurrentUserController
14
+ ).compile();
15
+ controller = unit;
16
+ service = unitRef.get(CurrentUserService);
17
+ });
18
+
19
+ it('should be defined', () => {
20
+ expect(controller).toBeDefined();
21
+ });
22
+
23
+ describe('completeOnboarding', () => {
24
+ it('delegates to CurrentUserService with the session user', async () => {
25
+ const session = Entity.userSession.build();
26
+ const updated = toCurrentUser(session.user);
27
+ service.completeOnboarding.mockResolvedValue(updated);
28
+
29
+ const result = await controller.completeOnboarding(session);
30
+
31
+ expect(service.completeOnboarding).toHaveBeenCalledWith(session.user);
32
+ expect(result).toBe(updated);
33
+ });
34
+ });
35
+
36
+ describe('getCurrentUser', () => {
37
+ it('should return a CurrentUserEntity from session user', () => {
38
+ const session = Entity.userSession.build();
39
+ const result = controller.getCurrentUser(session);
40
+
41
+ expect(CurrentUserEntity.safeParse(result).success).toBe(true);
42
+ expect(result.id).toBe(session.user.id);
43
+ expect(result.name).toBe(session.user.name);
44
+ expect(result.email).toBe(session.user.email);
45
+ });
46
+ });
47
+
48
+ // @feature:start media
49
+ describe('updateAvatar', () => {
50
+ const file = {
51
+ originalname: 'photo.jpg',
52
+ buffer: Buffer.from('photo-data'),
53
+ mimetype: 'image/jpeg'
54
+ } as Express.Multer.File;
55
+
56
+ it('should delegate to CurrentUserService.updateAvatar', async () => {
57
+ const session = Entity.userSession.build();
58
+ const fileEntity = Entity.file.build();
59
+ service.updateAvatar.mockResolvedValue(fileEntity);
60
+
61
+ const result = await controller.updateAvatar(session, file);
62
+
63
+ expect(service.updateAvatar).toHaveBeenCalledWith(session.user, file);
64
+ expect(result).toBe(fileEntity);
65
+ });
66
+
67
+ it('should pass the session user and uploaded file to the service', async () => {
68
+ const session = Entity.userSession.build();
69
+ const fileEntity = Entity.file.build();
70
+ service.updateAvatar.mockResolvedValue(fileEntity);
71
+ await controller.updateAvatar(session, file);
72
+
73
+ expect(service.updateAvatar).toHaveBeenCalledWith(session.user, file);
74
+ });
75
+ });
76
+ // @feature:end
77
+ });