create-einja-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +307 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +1041 -0
  4. package/dist/cli.js.map +1 -0
  5. package/package.json +62 -0
  6. package/templates/turborepo-pandacss/.biomeignore +15 -0
  7. package/templates/turborepo-pandacss/.claude/hooks/einja/biome-format.sh +49 -0
  8. package/templates/turborepo-pandacss/.claude/hooks/einja/design-doc-check.sh +61 -0
  9. package/templates/turborepo-pandacss/.claude/hooks/einja/detect-secrets.sh +62 -0
  10. package/templates/turborepo-pandacss/.claude/hooks/einja/large-file-warning.sh +42 -0
  11. package/templates/turborepo-pandacss/.claude/hooks/einja/playwright-resize.sh +36 -0
  12. package/templates/turborepo-pandacss/.claude/hooks/einja/typecheck.sh +37 -0
  13. package/templates/turborepo-pandacss/.claude/hooks/einja/unset-volta-recursion.sh +32 -0
  14. package/templates/turborepo-pandacss/.claude/hooks/einja/validate-git-commit.sh +239 -0
  15. package/templates/turborepo-pandacss/.claude/hooks/einja/warn-index-ts.sh +34 -0
  16. package/templates/turborepo-pandacss/.claude/hooks/einja/warn-relative-import.sh +48 -0
  17. package/templates/turborepo-pandacss/.claude/settings.json +174 -0
  18. package/templates/turborepo-pandacss/.claude/skills/create-einja-app-release/SKILL.md +186 -0
  19. package/templates/turborepo-pandacss/.claude/skills/dev-cli-release/SKILL.md +173 -0
  20. package/templates/turborepo-pandacss/.cursor/commands/spec-create.md +227 -0
  21. package/templates/turborepo-pandacss/.cursor/commands/start-dev.md +98 -0
  22. package/templates/turborepo-pandacss/.cursor/commands/task-exec.md +287 -0
  23. package/templates/turborepo-pandacss/.cursor/commands/task-vibe-kanban-loop.md +532 -0
  24. package/templates/turborepo-pandacss/.cursor/commands/update-docs-by-task-specs.md +448 -0
  25. package/templates/turborepo-pandacss/.cursor/mcp.json +45 -0
  26. package/templates/turborepo-pandacss/.cursor/rules/api-rules.mdc +171 -0
  27. package/templates/turborepo-pandacss/.cursor/rules/api-test-rules.mdc +181 -0
  28. package/templates/turborepo-pandacss/.cursor/rules/base-code.mdc +70 -0
  29. package/templates/turborepo-pandacss/.cursor/rules/base-commit-rules.mdc +174 -0
  30. package/templates/turborepo-pandacss/.cursor/rules/base-design.mdc +12 -0
  31. package/templates/turborepo-pandacss/.cursor/rules/base-rules.mdc +231 -0
  32. package/templates/turborepo-pandacss/.cursor/rules/error-handling-rules.mdc +188 -0
  33. package/templates/turborepo-pandacss/.cursor/rules/refactor-rules.mdc +93 -0
  34. package/templates/turborepo-pandacss/.dockerignore +126 -0
  35. package/templates/turborepo-pandacss/.einja-sync.json +35 -0
  36. package/templates/turborepo-pandacss/.env.ci +25 -0
  37. package/templates/turborepo-pandacss/.env.example +35 -0
  38. package/templates/turborepo-pandacss/.env.personal.example +27 -0
  39. package/templates/turborepo-pandacss/.envrc +4 -0
  40. package/templates/turborepo-pandacss/.gitattributes +5 -0
  41. package/templates/turborepo-pandacss/.husky/pre-commit +1 -0
  42. package/templates/turborepo-pandacss/.lintstagedrc.js +24 -0
  43. package/templates/turborepo-pandacss/.mcp.json +45 -0
  44. package/templates/turborepo-pandacss/.node-version +1 -0
  45. package/templates/turborepo-pandacss/.templateignore +60 -0
  46. package/templates/turborepo-pandacss/.vscode/extensions.json +3 -0
  47. package/templates/turborepo-pandacss/CLAUDE.md +415 -0
  48. package/templates/turborepo-pandacss/README.md +322 -0
  49. package/templates/turborepo-pandacss/apps/web/middleware.ts +28 -0
  50. package/templates/turborepo-pandacss/apps/web/next.config.ts +10 -0
  51. package/templates/turborepo-pandacss/apps/web/package.json +80 -0
  52. package/templates/turborepo-pandacss/apps/web/panda.config.ts +114 -0
  53. package/templates/turborepo-pandacss/apps/web/postcss.config.cjs +6 -0
  54. package/templates/turborepo-pandacss/apps/web/public/file.svg +1 -0
  55. package/templates/turborepo-pandacss/apps/web/public/globe.svg +1 -0
  56. package/templates/turborepo-pandacss/apps/web/public/next.svg +1 -0
  57. package/templates/turborepo-pandacss/apps/web/public/vercel.svg +1 -0
  58. package/templates/turborepo-pandacss/apps/web/public/window.svg +1 -0
  59. package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/dashboard/page.tsx +79 -0
  60. package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/data/_components/UserTable.tsx +203 -0
  61. package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/data/page.tsx +57 -0
  62. package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/layout-client.tsx +31 -0
  63. package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/layout.tsx +17 -0
  64. package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/profile/page.tsx +59 -0
  65. package/templates/turborepo-pandacss/apps/web/src/app/api/auth/[...nextauth]/route.ts +3 -0
  66. package/templates/turborepo-pandacss/apps/web/src/app/api/auth/signup/route.ts +70 -0
  67. package/templates/turborepo-pandacss/apps/web/src/app/error.tsx +106 -0
  68. package/templates/turborepo-pandacss/apps/web/src/app/favicon.ico +0 -0
  69. package/templates/turborepo-pandacss/apps/web/src/app/global-error.tsx +110 -0
  70. package/templates/turborepo-pandacss/apps/web/src/app/globals.css +121 -0
  71. package/templates/turborepo-pandacss/apps/web/src/app/layout.tsx +28 -0
  72. package/templates/turborepo-pandacss/apps/web/src/app/not-found.tsx +54 -0
  73. package/templates/turborepo-pandacss/apps/web/src/app/page.module.css +165 -0
  74. package/templates/turborepo-pandacss/apps/web/src/app/page.test.tsx +52 -0
  75. package/templates/turborepo-pandacss/apps/web/src/app/page.tsx +284 -0
  76. package/templates/turborepo-pandacss/apps/web/src/app/signin/page.tsx +296 -0
  77. package/templates/turborepo-pandacss/apps/web/src/app/signup/page.tsx +395 -0
  78. package/templates/turborepo-pandacss/apps/web/src/application/use-cases/UserUseCases.test.ts +229 -0
  79. package/templates/turborepo-pandacss/apps/web/src/application/use-cases/UserUseCases.ts +115 -0
  80. package/templates/turborepo-pandacss/apps/web/src/components/auth/login-button.tsx +35 -0
  81. package/templates/turborepo-pandacss/apps/web/src/components/auth/logout-button.tsx +24 -0
  82. package/templates/turborepo-pandacss/apps/web/src/components/auth/user-avatar.test.tsx +68 -0
  83. package/templates/turborepo-pandacss/apps/web/src/components/auth/user-avatar.tsx +43 -0
  84. package/templates/turborepo-pandacss/apps/web/src/components/dashboard/dashboard-stats.tsx +128 -0
  85. package/templates/turborepo-pandacss/apps/web/src/components/providers/query-provider.tsx +30 -0
  86. package/templates/turborepo-pandacss/apps/web/src/components/providers/session-provider.tsx +12 -0
  87. package/templates/turborepo-pandacss/apps/web/src/components/shared/Sidebar.tsx +175 -0
  88. package/templates/turborepo-pandacss/apps/web/src/components/shared/header.tsx +166 -0
  89. package/templates/turborepo-pandacss/apps/web/src/components/ui/accordion.tsx +64 -0
  90. package/templates/turborepo-pandacss/apps/web/src/components/ui/alert-dialog.tsx +135 -0
  91. package/templates/turborepo-pandacss/apps/web/src/components/ui/alert.tsx +60 -0
  92. package/templates/turborepo-pandacss/apps/web/src/components/ui/aspect-ratio.tsx +9 -0
  93. package/templates/turborepo-pandacss/apps/web/src/components/ui/avatar.tsx +41 -0
  94. package/templates/turborepo-pandacss/apps/web/src/components/ui/badge.tsx +39 -0
  95. package/templates/turborepo-pandacss/apps/web/src/components/ui/breadcrumb.tsx +101 -0
  96. package/templates/turborepo-pandacss/apps/web/src/components/ui/button.tsx +56 -0
  97. package/templates/turborepo-pandacss/apps/web/src/components/ui/card.tsx +75 -0
  98. package/templates/turborepo-pandacss/apps/web/src/components/ui/checkbox.tsx +29 -0
  99. package/templates/turborepo-pandacss/apps/web/src/components/ui/data-table.tsx +189 -0
  100. package/templates/turborepo-pandacss/apps/web/src/components/ui/dialog-hook.tsx +210 -0
  101. package/templates/turborepo-pandacss/apps/web/src/components/ui/dialog.tsx +129 -0
  102. package/templates/turborepo-pandacss/apps/web/src/components/ui/drawer.tsx +124 -0
  103. package/templates/turborepo-pandacss/apps/web/src/components/ui/dropdown-menu.tsx +228 -0
  104. package/templates/turborepo-pandacss/apps/web/src/components/ui/form.tsx +152 -0
  105. package/templates/turborepo-pandacss/apps/web/src/components/ui/hover-card.tsx +38 -0
  106. package/templates/turborepo-pandacss/apps/web/src/components/ui/input.tsx +21 -0
  107. package/templates/turborepo-pandacss/apps/web/src/components/ui/label.tsx +21 -0
  108. package/templates/turborepo-pandacss/apps/web/src/components/ui/pagination.tsx +105 -0
  109. package/templates/turborepo-pandacss/apps/web/src/components/ui/popover.tsx +42 -0
  110. package/templates/turborepo-pandacss/apps/web/src/components/ui/progress.tsx +28 -0
  111. package/templates/turborepo-pandacss/apps/web/src/components/ui/select.tsx +170 -0
  112. package/templates/turborepo-pandacss/apps/web/src/components/ui/separator.tsx +28 -0
  113. package/templates/turborepo-pandacss/apps/web/src/components/ui/skeleton.tsx +13 -0
  114. package/templates/turborepo-pandacss/apps/web/src/components/ui/sonner.tsx +25 -0
  115. package/templates/turborepo-pandacss/apps/web/src/components/ui/table.tsx +92 -0
  116. package/templates/turborepo-pandacss/apps/web/src/components/ui/tabs.tsx +54 -0
  117. package/templates/turborepo-pandacss/apps/web/src/components/ui/textarea.tsx +18 -0
  118. package/templates/turborepo-pandacss/apps/web/src/components/ui/tooltip.tsx +57 -0
  119. package/templates/turborepo-pandacss/apps/web/src/components/ui/typography.tsx +158 -0
  120. package/templates/turborepo-pandacss/apps/web/src/lib/auth/guard.ts +36 -0
  121. package/templates/turborepo-pandacss/apps/web/src/lib/auth/index.ts +22 -0
  122. package/templates/turborepo-pandacss/apps/web/src/lib/prisma.ts +3 -0
  123. package/templates/turborepo-pandacss/apps/web/src/lib/utils.ts +6 -0
  124. package/templates/turborepo-pandacss/apps/web/test/globals.d.ts +1 -0
  125. package/templates/turborepo-pandacss/apps/web/test/matchers.d.ts +1 -0
  126. package/templates/turborepo-pandacss/apps/web/test/setup.ts +22 -0
  127. package/templates/turborepo-pandacss/apps/web/tsconfig.json +37 -0
  128. package/templates/turborepo-pandacss/apps/web/vitest.config.ts +20 -0
  129. package/templates/turborepo-pandacss/apps/web/vitest.d.ts +2 -0
  130. package/templates/turborepo-pandacss/biome.json +60 -0
  131. package/templates/turborepo-pandacss/components.json +21 -0
  132. package/templates/turborepo-pandacss/docker-compose.yml +27 -0
  133. package/templates/turborepo-pandacss/middleware.ts +32 -0
  134. package/templates/turborepo-pandacss/next.config.ts +10 -0
  135. package/templates/turborepo-pandacss/package-lock.json +9346 -0
  136. package/templates/turborepo-pandacss/package.json +64 -0
  137. package/templates/turborepo-pandacss/packages/config/package.json +41 -0
  138. package/templates/turborepo-pandacss/packages/config/panda.config.ts +114 -0
  139. package/templates/turborepo-pandacss/packages/config/src/index.ts +24 -0
  140. package/templates/turborepo-pandacss/packages/config/src/worktree-config-loader.ts +129 -0
  141. package/templates/turborepo-pandacss/packages/config/src/worktree-config.ts +75 -0
  142. package/templates/turborepo-pandacss/packages/config/tsconfig.build.json +19 -0
  143. package/templates/turborepo-pandacss/packages/config/tsconfig.json +24 -0
  144. package/templates/turborepo-pandacss/packages/config/typescript/base.json +19 -0
  145. package/templates/turborepo-pandacss/packages/front-core/package.json +24 -0
  146. package/templates/turborepo-pandacss/packages/front-core/src/auth/config.ts +84 -0
  147. package/templates/turborepo-pandacss/packages/front-core/src/auth/index.ts +5 -0
  148. package/templates/turborepo-pandacss/packages/front-core/src/auth/types/next-auth.d.ts +20 -0
  149. package/templates/turborepo-pandacss/packages/front-core/src/auth/utils.ts +29 -0
  150. package/templates/turborepo-pandacss/packages/front-core/src/context/index.ts +2 -0
  151. package/templates/turborepo-pandacss/packages/front-core/src/hooks/index.ts +2 -0
  152. package/templates/turborepo-pandacss/packages/front-core/src/index.ts +4 -0
  153. package/templates/turborepo-pandacss/packages/front-core/src/utils/index.ts +2 -0
  154. package/templates/turborepo-pandacss/packages/front-core/tsconfig.json +14 -0
  155. package/templates/turborepo-pandacss/packages/server-core/package.json +32 -0
  156. package/templates/turborepo-pandacss/packages/server-core/prisma/schema.prisma +102 -0
  157. package/templates/turborepo-pandacss/packages/server-core/prisma/seed.ts +67 -0
  158. package/templates/turborepo-pandacss/packages/server-core/prisma.config.ts +8 -0
  159. package/templates/turborepo-pandacss/packages/server-core/src/__generated__/fabbrica/index.d.ts +270 -0
  160. package/templates/turborepo-pandacss/packages/server-core/src/__generated__/fabbrica/index.js +484 -0
  161. package/templates/turborepo-pandacss/packages/server-core/src/core/result.test.ts +78 -0
  162. package/templates/turborepo-pandacss/packages/server-core/src/core/result.ts +53 -0
  163. package/templates/turborepo-pandacss/packages/server-core/src/domain/.gitkeep +0 -0
  164. package/templates/turborepo-pandacss/packages/server-core/src/domain/entities/User.test.ts +232 -0
  165. package/templates/turborepo-pandacss/packages/server-core/src/domain/entities/User.ts +105 -0
  166. package/templates/turborepo-pandacss/packages/server-core/src/domain/repository-interfaces/IUserRepository.ts +101 -0
  167. package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/client.ts +15 -0
  168. package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/mappers/UserMapper.test.ts +278 -0
  169. package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/mappers/UserMapper.ts +103 -0
  170. package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/repositories/UserRepository.test.ts +317 -0
  171. package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/repositories/UserRepository.ts +169 -0
  172. package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/index.ts +22 -0
  173. package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/user.factory.ts +123 -0
  174. package/templates/turborepo-pandacss/packages/server-core/src/testing/fixtures/users.ts +92 -0
  175. package/templates/turborepo-pandacss/packages/server-core/src/testing/helpers/date.ts +49 -0
  176. package/templates/turborepo-pandacss/packages/server-core/src/testing/helpers/password.ts +50 -0
  177. package/templates/turborepo-pandacss/packages/server-core/src/testing/index.ts +26 -0
  178. package/templates/turborepo-pandacss/packages/server-core/tsconfig.json +14 -0
  179. package/templates/turborepo-pandacss/packages/server-core/vitest.config.ts +15 -0
  180. package/templates/turborepo-pandacss/packages/ui/package.json +37 -0
  181. package/templates/turborepo-pandacss/packages/ui/src/accordion.tsx +66 -0
  182. package/templates/turborepo-pandacss/packages/ui/src/alert-dialog.tsx +157 -0
  183. package/templates/turborepo-pandacss/packages/ui/src/alert.tsx +66 -0
  184. package/templates/turborepo-pandacss/packages/ui/src/aspect-ratio.tsx +11 -0
  185. package/templates/turborepo-pandacss/packages/ui/src/avatar.tsx +53 -0
  186. package/templates/turborepo-pandacss/packages/ui/src/badge.tsx +46 -0
  187. package/templates/turborepo-pandacss/packages/ui/src/breadcrumb.tsx +108 -0
  188. package/templates/turborepo-pandacss/packages/ui/src/button.tsx +59 -0
  189. package/templates/turborepo-pandacss/packages/ui/src/card.tsx +92 -0
  190. package/templates/turborepo-pandacss/packages/ui/src/checkbox.tsx +32 -0
  191. package/templates/turborepo-pandacss/packages/ui/src/data-table.tsx +216 -0
  192. package/templates/turborepo-pandacss/packages/ui/src/dialog-hook.tsx +226 -0
  193. package/templates/turborepo-pandacss/packages/ui/src/dialog.tsx +143 -0
  194. package/templates/turborepo-pandacss/packages/ui/src/drawer.tsx +135 -0
  195. package/templates/turborepo-pandacss/packages/ui/src/dropdown-menu.tsx +257 -0
  196. package/templates/turborepo-pandacss/packages/ui/src/form.tsx +168 -0
  197. package/templates/turborepo-pandacss/packages/ui/src/hover-card.tsx +44 -0
  198. package/templates/turborepo-pandacss/packages/ui/src/input.tsx +21 -0
  199. package/templates/turborepo-pandacss/packages/ui/src/label.tsx +24 -0
  200. package/templates/turborepo-pandacss/packages/ui/src/lib/utils.ts +6 -0
  201. package/templates/turborepo-pandacss/packages/ui/src/pagination.tsx +126 -0
  202. package/templates/turborepo-pandacss/packages/ui/src/popover.tsx +48 -0
  203. package/templates/turborepo-pandacss/packages/ui/src/progress.tsx +31 -0
  204. package/templates/turborepo-pandacss/packages/ui/src/select.tsx +185 -0
  205. package/templates/turborepo-pandacss/packages/ui/src/separator.tsx +28 -0
  206. package/templates/turborepo-pandacss/packages/ui/src/skeleton.tsx +13 -0
  207. package/templates/turborepo-pandacss/packages/ui/src/sonner.tsx +25 -0
  208. package/templates/turborepo-pandacss/packages/ui/src/table.tsx +116 -0
  209. package/templates/turborepo-pandacss/packages/ui/src/tabs.tsx +66 -0
  210. package/templates/turborepo-pandacss/packages/ui/src/textarea.tsx +18 -0
  211. package/templates/turborepo-pandacss/packages/ui/src/tooltip.tsx +61 -0
  212. package/templates/turborepo-pandacss/packages/ui/src/typography.tsx +187 -0
  213. package/templates/turborepo-pandacss/packages/ui/tsconfig.json +20 -0
  214. package/templates/turborepo-pandacss/panda.config.ts +114 -0
  215. package/templates/turborepo-pandacss/pnpm-lock.yaml +9032 -0
  216. package/templates/turborepo-pandacss/pnpm-workspace.yaml +11 -0
  217. package/templates/turborepo-pandacss/postcss.config.cjs +6 -0
  218. package/templates/turborepo-pandacss/prisma/schema.prisma +82 -0
  219. package/templates/turborepo-pandacss/public/file.svg +1 -0
  220. package/templates/turborepo-pandacss/public/globe.svg +1 -0
  221. package/templates/turborepo-pandacss/public/next.svg +1 -0
  222. package/templates/turborepo-pandacss/public/vercel.svg +1 -0
  223. package/templates/turborepo-pandacss/public/window.svg +1 -0
  224. package/templates/turborepo-pandacss/scripts/cli-template-update.ts +387 -0
  225. package/templates/turborepo-pandacss/scripts/env-show.ts +129 -0
  226. package/templates/turborepo-pandacss/scripts/env.ts +555 -0
  227. package/templates/turborepo-pandacss/scripts/init.sh +92 -0
  228. package/templates/turborepo-pandacss/scripts/setup-dev.ts +640 -0
  229. package/templates/turborepo-pandacss/scripts/template-update.ts +277 -0
  230. package/templates/turborepo-pandacss/scripts/worktree/dev.ts +872 -0
  231. package/templates/turborepo-pandacss/test/globals.d.ts +1 -0
  232. package/templates/turborepo-pandacss/test/setup.ts +22 -0
  233. package/templates/turborepo-pandacss/tsconfig.json +46 -0
  234. package/templates/turborepo-pandacss/turbo.json +57 -0
  235. package/templates/turborepo-pandacss/vitest.config.ts +20 -0
@@ -0,0 +1,232 @@
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { UserFactory, initialize } from "../../testing";
3
+ import { User, type UserProps } from "./User";
4
+
5
+ describe("User Entity", () => {
6
+ beforeAll(() => {
7
+ // ドメインエンティティのテストではPrismaクライアントは使用しないため、空のオブジェクトを渡す
8
+ // biome-ignore lint/suspicious/noExplicitAny: test fixture initialization
9
+ initialize({ prisma: {} as any });
10
+ });
11
+
12
+ describe("constructor", () => {
13
+ it("有効なプロパティでエンティティが生成される", async () => {
14
+ // Given
15
+ const props = await UserFactory.build();
16
+
17
+ // When
18
+ const user = new User(props);
19
+
20
+ // Then
21
+ expect(user.id).toBeDefined();
22
+ expect(user.email).toBeDefined();
23
+ expect(user.name).toBeDefined();
24
+ expect(user.status).toBeDefined();
25
+ expect(user.role).toBeDefined();
26
+ expect(user.createdAt).toBeInstanceOf(Date);
27
+ expect(user.lastLogin).toBeInstanceOf(Date);
28
+ });
29
+
30
+ it("nameがnullでもエンティティが生成される", async () => {
31
+ // Given
32
+ const props = await UserFactory.build({ name: null });
33
+
34
+ // When
35
+ const user = new User(props);
36
+
37
+ // Then
38
+ expect(user.name).toBeNull();
39
+ });
40
+
41
+ it("lastLoginがnullでもエンティティが生成される", async () => {
42
+ // Given
43
+ const props = await UserFactory.build({ lastLogin: null });
44
+
45
+ // When
46
+ const user = new User(props);
47
+
48
+ // Then
49
+ expect(user.lastLogin).toBeNull();
50
+ });
51
+
52
+ it("全てのstatus値でエンティティが生成される", async () => {
53
+ // Given & When & Then
54
+ const statuses = ["active", "inactive", "pending"] as const;
55
+ for (const status of statuses) {
56
+ const props = await UserFactory.build({ status });
57
+ const user = new User(props);
58
+ expect(user.status).toBe(status);
59
+ }
60
+ });
61
+
62
+ it("全てのrole値でエンティティが生成される", async () => {
63
+ // Given & When & Then
64
+ const roles = ["admin", "user", "moderator"] as const;
65
+ for (const role of roles) {
66
+ const props = await UserFactory.build({ role });
67
+ const user = new User(props);
68
+ expect(user.role).toBe(role);
69
+ }
70
+ });
71
+ });
72
+
73
+ describe("withLastLogin", () => {
74
+ it("最終ログイン日時を更新した新しいインスタンスを返す", async () => {
75
+ // Given
76
+ const props = await UserFactory.build({ lastLogin: null });
77
+ const originalUser = new User(props);
78
+ const newLoginTime = new Date("2025-01-03T12:00:00Z");
79
+
80
+ // When
81
+ const updatedUser = originalUser.withLastLogin(newLoginTime);
82
+
83
+ // Then
84
+ expect(updatedUser.lastLogin).toEqual(newLoginTime);
85
+ expect(updatedUser).not.toBe(originalUser);
86
+ });
87
+
88
+ it("元のインスタンスは変更されない(イミュータビリティ)", async () => {
89
+ // Given
90
+ const originalLastLogin = new Date("2025-01-02T00:00:00Z");
91
+ const props = await UserFactory.build({ lastLogin: originalLastLogin });
92
+ const originalUser = new User(props);
93
+ const newLoginTime = new Date("2025-01-03T12:00:00Z");
94
+
95
+ // When
96
+ originalUser.withLastLogin(newLoginTime);
97
+
98
+ // Then
99
+ expect(originalUser.lastLogin).toEqual(originalLastLogin);
100
+ });
101
+
102
+ it("他のプロパティは維持される", async () => {
103
+ // Given
104
+ const props = await UserFactory.build();
105
+ const originalUser = new User(props);
106
+ const newLoginTime = new Date("2025-01-03T12:00:00Z");
107
+
108
+ // When
109
+ const updatedUser = originalUser.withLastLogin(newLoginTime);
110
+
111
+ // Then
112
+ expect(updatedUser.id).toBe(originalUser.id);
113
+ expect(updatedUser.email).toBe(originalUser.email);
114
+ expect(updatedUser.name).toBe(originalUser.name);
115
+ expect(updatedUser.status).toBe(originalUser.status);
116
+ expect(updatedUser.role).toBe(originalUser.role);
117
+ expect(updatedUser.createdAt).toEqual(originalUser.createdAt);
118
+ });
119
+ });
120
+
121
+ describe("withStatus", () => {
122
+ it("ステータスを更新した新しいインスタンスを返す", async () => {
123
+ // Given
124
+ const props = await UserFactory.build({ status: "pending" });
125
+ const originalUser = new User(props);
126
+
127
+ // When
128
+ const updatedUser = originalUser.withStatus("active");
129
+
130
+ // Then
131
+ expect(updatedUser.status).toBe("active");
132
+ expect(updatedUser).not.toBe(originalUser);
133
+ });
134
+
135
+ it("元のインスタンスは変更されない(イミュータビリティ)", async () => {
136
+ // Given
137
+ const props = await UserFactory.build({ status: "pending" });
138
+ const originalUser = new User(props);
139
+
140
+ // When
141
+ originalUser.withStatus("active");
142
+
143
+ // Then
144
+ expect(originalUser.status).toBe("pending");
145
+ });
146
+ });
147
+
148
+ describe("withRole", () => {
149
+ it("ロールを更新した新しいインスタンスを返す", async () => {
150
+ // Given
151
+ const props = await UserFactory.build({ role: "user" });
152
+ const originalUser = new User(props);
153
+
154
+ // When
155
+ const updatedUser = originalUser.withRole("admin");
156
+
157
+ // Then
158
+ expect(updatedUser.role).toBe("admin");
159
+ expect(updatedUser).not.toBe(originalUser);
160
+ });
161
+
162
+ it("元のインスタンスは変更されない(イミュータビリティ)", async () => {
163
+ // Given
164
+ const props = await UserFactory.build({ role: "user" });
165
+ const originalUser = new User(props);
166
+
167
+ // When
168
+ originalUser.withRole("admin");
169
+
170
+ // Then
171
+ expect(originalUser.role).toBe("user");
172
+ });
173
+ });
174
+
175
+ describe("isActive", () => {
176
+ it("statusがactiveの場合、trueを返す", async () => {
177
+ // Given
178
+ const props = await UserFactory.build({ status: "active" });
179
+ const user = new User(props);
180
+
181
+ // When & Then
182
+ expect(user.isActive()).toBe(true);
183
+ });
184
+
185
+ it("statusがinactiveの場合、falseを返す", async () => {
186
+ // Given
187
+ const props = await UserFactory.build({ status: "inactive" });
188
+ const user = new User(props);
189
+
190
+ // When & Then
191
+ expect(user.isActive()).toBe(false);
192
+ });
193
+
194
+ it("statusがpendingの場合、falseを返す", async () => {
195
+ // Given
196
+ const props = await UserFactory.build({ status: "pending" });
197
+ const user = new User(props);
198
+
199
+ // When & Then
200
+ expect(user.isActive()).toBe(false);
201
+ });
202
+ });
203
+
204
+ describe("isAdmin", () => {
205
+ it("roleがadminの場合、trueを返す", async () => {
206
+ // Given
207
+ const props = await UserFactory.build({ role: "admin" });
208
+ const user = new User(props);
209
+
210
+ // When & Then
211
+ expect(user.isAdmin()).toBe(true);
212
+ });
213
+
214
+ it("roleがuserの場合、falseを返す", async () => {
215
+ // Given
216
+ const props = await UserFactory.build({ role: "user" });
217
+ const user = new User(props);
218
+
219
+ // When & Then
220
+ expect(user.isAdmin()).toBe(false);
221
+ });
222
+
223
+ it("roleがmoderatorの場合、falseを返す", async () => {
224
+ // Given
225
+ const props = await UserFactory.build({ role: "moderator" });
226
+ const user = new User(props);
227
+
228
+ // When & Then
229
+ expect(user.isAdmin()).toBe(false);
230
+ });
231
+ });
232
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * User Entity
3
+ *
4
+ * ドメイン層のユーザーエンティティ。
5
+ * Prismaモデルから独立したドメインオブジェクトとして定義。
6
+ */
7
+
8
+ export type UserStatus = "active" | "inactive" | "pending";
9
+
10
+ export type UserRole = "admin" | "user" | "moderator";
11
+
12
+ export interface UserProps {
13
+ readonly id: string;
14
+ readonly email: string;
15
+ readonly name: string | null;
16
+ readonly status: UserStatus;
17
+ readonly role: UserRole;
18
+ readonly createdAt: Date;
19
+ readonly lastLogin: Date | null;
20
+ }
21
+
22
+ /**
23
+ * ユーザーエンティティ
24
+ *
25
+ * イミュータブルなドメインオブジェクト。
26
+ * 状態変更は新しいインスタンスを生成することで行う。
27
+ */
28
+ export class User {
29
+ readonly id: string;
30
+ readonly email: string;
31
+ readonly name: string | null;
32
+ readonly status: UserStatus;
33
+ readonly role: UserRole;
34
+ readonly createdAt: Date;
35
+ readonly lastLogin: Date | null;
36
+
37
+ constructor(props: UserProps) {
38
+ this.id = props.id;
39
+ this.email = props.email;
40
+ this.name = props.name;
41
+ this.status = props.status;
42
+ this.role = props.role;
43
+ this.createdAt = props.createdAt;
44
+ this.lastLogin = props.lastLogin;
45
+ }
46
+
47
+ /**
48
+ * 最終ログイン日時を更新した新しいUserインスタンスを返す
49
+ */
50
+ withLastLogin(loginTime: Date): User {
51
+ return new User({
52
+ id: this.id,
53
+ email: this.email,
54
+ name: this.name,
55
+ status: this.status,
56
+ role: this.role,
57
+ createdAt: this.createdAt,
58
+ lastLogin: loginTime,
59
+ });
60
+ }
61
+
62
+ /**
63
+ * ステータスを更新した新しいUserインスタンスを返す
64
+ */
65
+ withStatus(status: UserStatus): User {
66
+ return new User({
67
+ id: this.id,
68
+ email: this.email,
69
+ name: this.name,
70
+ status,
71
+ role: this.role,
72
+ createdAt: this.createdAt,
73
+ lastLogin: this.lastLogin,
74
+ });
75
+ }
76
+
77
+ /**
78
+ * ロールを更新した新しいUserインスタンスを返す
79
+ */
80
+ withRole(role: UserRole): User {
81
+ return new User({
82
+ id: this.id,
83
+ email: this.email,
84
+ name: this.name,
85
+ status: this.status,
86
+ role,
87
+ createdAt: this.createdAt,
88
+ lastLogin: this.lastLogin,
89
+ });
90
+ }
91
+
92
+ /**
93
+ * アクティブなユーザーかどうか
94
+ */
95
+ isActive(): boolean {
96
+ return this.status === "active";
97
+ }
98
+
99
+ /**
100
+ * 管理者かどうか
101
+ */
102
+ isAdmin(): boolean {
103
+ return this.role === "admin";
104
+ }
105
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * IUserRepository
3
+ *
4
+ * ユーザーリポジトリのインターフェース。
5
+ * Domain層で定義し、Infrastructure層で実装する。
6
+ */
7
+
8
+ import type { Result } from "../../core/result";
9
+ import type { User, UserRole, UserStatus } from "../entities/User";
10
+
11
+ /**
12
+ * ユーザー検索条件
13
+ */
14
+ export interface UserSearchCriteria {
15
+ /** ユーザーID */
16
+ readonly id?: string;
17
+ /** メールアドレス */
18
+ readonly email?: string;
19
+ /** ステータス */
20
+ readonly status?: UserStatus;
21
+ /** ロール */
22
+ readonly role?: UserRole;
23
+ /** 検索テキスト(名前やメールアドレスに対する部分一致) */
24
+ readonly search?: string;
25
+ }
26
+
27
+ /**
28
+ * ページネーションオプション
29
+ */
30
+ export interface PaginationOptions {
31
+ /** ページ番号(1始まり) */
32
+ readonly page?: number;
33
+ /** 1ページあたりの件数 */
34
+ readonly limit?: number;
35
+ }
36
+
37
+ /**
38
+ * ページネーション付きの結果
39
+ */
40
+ export interface PaginatedResult<T> {
41
+ /** 結果アイテム */
42
+ readonly items: readonly T[];
43
+ /** 総件数 */
44
+ readonly total: number;
45
+ /** 現在のページ番号 */
46
+ readonly page: number;
47
+ /** 1ページあたりの件数 */
48
+ readonly limit: number;
49
+ /** 総ページ数 */
50
+ readonly totalPages: number;
51
+ }
52
+
53
+ /**
54
+ * ユーザーリポジトリインターフェース
55
+ */
56
+ export interface IUserRepository {
57
+ /**
58
+ * 検索条件に基づいてユーザーを検索する
59
+ *
60
+ * @param criteria - 検索条件
61
+ * @param pagination - ページネーションオプション
62
+ * @returns ページネーション付きのユーザーリスト
63
+ */
64
+ search(
65
+ criteria: UserSearchCriteria,
66
+ pagination?: PaginationOptions,
67
+ ): Promise<Result<PaginatedResult<User>, Error>>;
68
+
69
+ /**
70
+ * 検索条件に一致する単一のユーザーを取得する
71
+ *
72
+ * @param criteria - 検索条件
73
+ * @returns 見つかったユーザー、または見つからない場合はnull
74
+ */
75
+ find(criteria: UserSearchCriteria): Promise<Result<User | null, Error>>;
76
+
77
+ /**
78
+ * IDでユーザーを取得する
79
+ *
80
+ * @param id - ユーザーID
81
+ * @returns 見つかったユーザー、または見つからない場合はnull
82
+ */
83
+ findById(id: string): Promise<Result<User | null, Error>>;
84
+
85
+ /**
86
+ * メールアドレスでユーザーを取得する
87
+ *
88
+ * @param email - メールアドレス
89
+ * @returns 見つかったユーザー、または見つからない場合はnull
90
+ */
91
+ findByEmail(email: string): Promise<Result<User | null, Error>>;
92
+
93
+ /**
94
+ * ユーザーの最終ログイン日時を更新する
95
+ *
96
+ * @param id - ユーザーID
97
+ * @param loginTime - ログイン日時
98
+ * @returns 成功時はvoid、失敗時はエラー
99
+ */
100
+ updateLastLogin(id: string, loginTime: Date): Promise<Result<void, Error>>;
101
+ }
@@ -0,0 +1,15 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+
3
+ const globalForPrisma = globalThis as unknown as {
4
+ prisma: PrismaClient | undefined;
5
+ };
6
+
7
+ export const prisma =
8
+ globalForPrisma.prisma ??
9
+ new PrismaClient({
10
+ log: ["query"],
11
+ });
12
+
13
+ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
14
+
15
+ export * from "@prisma/client";