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,169 @@
1
+ /**
2
+ * UserRepository
3
+ *
4
+ * IUserRepositoryの実装。Prismaを使用してユーザーデータを操作する。
5
+ */
6
+
7
+ import type { Prisma } from "@prisma/client";
8
+ import type { User } from "../../../domain/entities/User";
9
+ import type {
10
+ IUserRepository,
11
+ PaginatedResult,
12
+ PaginationOptions,
13
+ UserSearchCriteria,
14
+ } from "../../../domain/repository-interfaces/IUserRepository";
15
+ import { type Result, failure, success } from "../../../core/result";
16
+ import { prisma } from "../client";
17
+ import { UserMapper } from "../mappers/UserMapper";
18
+
19
+ /** デフォルトのページネーション設定 */
20
+ const DEFAULT_PAGE = 1;
21
+ const DEFAULT_LIMIT = 10;
22
+
23
+ /**
24
+ * 検索条件をPrismaのwhere句に変換
25
+ */
26
+ function buildWhereClause(criteria: UserSearchCriteria): Prisma.UserWhereInput {
27
+ const where: Prisma.UserWhereInput = {};
28
+
29
+ if (criteria.id !== undefined) {
30
+ where.id = criteria.id;
31
+ }
32
+
33
+ if (criteria.email !== undefined) {
34
+ where.email = criteria.email;
35
+ }
36
+
37
+ if (criteria.status !== undefined) {
38
+ where.status = criteria.status;
39
+ }
40
+
41
+ if (criteria.role !== undefined) {
42
+ where.role = criteria.role;
43
+ }
44
+
45
+ if (criteria.search !== undefined && criteria.search.trim() !== "") {
46
+ where.OR = [
47
+ { name: { contains: criteria.search, mode: "insensitive" } },
48
+ { email: { contains: criteria.search, mode: "insensitive" } },
49
+ ];
50
+ }
51
+
52
+ return where;
53
+ }
54
+
55
+ /**
56
+ * UserRepository実装
57
+ */
58
+ export const userRepository: IUserRepository = {
59
+ async search(
60
+ criteria: UserSearchCriteria,
61
+ pagination?: PaginationOptions,
62
+ ): Promise<Result<PaginatedResult<User>, Error>> {
63
+ try {
64
+ const page = pagination?.page ?? DEFAULT_PAGE;
65
+ const limit = pagination?.limit ?? DEFAULT_LIMIT;
66
+ const skip = (page - 1) * limit;
67
+
68
+ const where = buildWhereClause(criteria);
69
+
70
+ const [users, total] = await Promise.all([
71
+ prisma.user.findMany({
72
+ where,
73
+ skip,
74
+ take: limit,
75
+ orderBy: { createdAt: "desc" },
76
+ }),
77
+ prisma.user.count({ where }),
78
+ ]);
79
+
80
+ const items = users.map(UserMapper.toDomain);
81
+ const totalPages = Math.ceil(total / limit);
82
+
83
+ return success({
84
+ items,
85
+ total,
86
+ page,
87
+ limit,
88
+ totalPages,
89
+ });
90
+ } catch (error) {
91
+ return failure(
92
+ error instanceof Error ? error : new Error("Unknown error occurred during user search"),
93
+ );
94
+ }
95
+ },
96
+
97
+ async find(criteria: UserSearchCriteria): Promise<Result<User | null, Error>> {
98
+ try {
99
+ const where = buildWhereClause(criteria);
100
+
101
+ const user = await prisma.user.findFirst({ where });
102
+
103
+ if (!user) {
104
+ return success(null);
105
+ }
106
+
107
+ return success(UserMapper.toDomain(user));
108
+ } catch (error) {
109
+ return failure(
110
+ error instanceof Error ? error : new Error("Unknown error occurred during user find"),
111
+ );
112
+ }
113
+ },
114
+
115
+ async findById(id: string): Promise<Result<User | null, Error>> {
116
+ try {
117
+ const user = await prisma.user.findUnique({
118
+ where: { id },
119
+ });
120
+
121
+ if (!user) {
122
+ return success(null);
123
+ }
124
+
125
+ return success(UserMapper.toDomain(user));
126
+ } catch (error) {
127
+ return failure(
128
+ error instanceof Error ? error : new Error("Unknown error occurred during user findById"),
129
+ );
130
+ }
131
+ },
132
+
133
+ async findByEmail(email: string): Promise<Result<User | null, Error>> {
134
+ try {
135
+ const user = await prisma.user.findUnique({
136
+ where: { email },
137
+ });
138
+
139
+ if (!user) {
140
+ return success(null);
141
+ }
142
+
143
+ return success(UserMapper.toDomain(user));
144
+ } catch (error) {
145
+ return failure(
146
+ error instanceof Error
147
+ ? error
148
+ : new Error("Unknown error occurred during user findByEmail"),
149
+ );
150
+ }
151
+ },
152
+
153
+ async updateLastLogin(id: string, loginTime: Date): Promise<Result<void, Error>> {
154
+ try {
155
+ await prisma.user.update({
156
+ where: { id },
157
+ data: { lastLogin: loginTime },
158
+ });
159
+
160
+ return success(undefined);
161
+ } catch (error) {
162
+ return failure(
163
+ error instanceof Error
164
+ ? error
165
+ : new Error("Unknown error occurred during updateLastLogin"),
166
+ );
167
+ }
168
+ },
169
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ファクトリーのエクスポート
3
+ */
4
+
5
+ // fabbricaの初期化関数をエクスポート
6
+ export { initialize } from "../../__generated__/fabbrica";
7
+
8
+ // ユーザーファクトリー
9
+ export {
10
+ UserFactory,
11
+ ActiveUserFactory,
12
+ InactiveUserFactory,
13
+ PendingUserFactory,
14
+ AdminUserFactory,
15
+ ModeratorUserFactory,
16
+ VerifiedUserFactory,
17
+ buildUserProps,
18
+ } from "./user.factory";
19
+
20
+ // 将来的に他のモデルのファクトリーもここに追加
21
+ // export { AccountFactory } from "./account.factory";
22
+ // export { SessionFactory } from "./session.factory";
@@ -0,0 +1,123 @@
1
+ import type { Prisma } from "@prisma/client";
2
+ import { UserRole, UserStatus } from "@prisma/client";
3
+ import { defineUserFactory } from "../../__generated__/fabbrica";
4
+ import { faker } from "@faker-js/faker/locale/ja";
5
+ import { getDefaultHashedPassword } from "../helpers/password";
6
+ import { randomDateInPastDays } from "../helpers/date";
7
+ import type { UserProps } from "../../domain/entities/User";
8
+
9
+ /**
10
+ * ユーザーファクトリーの基本定義
11
+ * @faker-js/fakerを使用してリアルなダミーデータを生成
12
+ */
13
+ export const UserFactory = defineUserFactory({
14
+ defaultData: async () => ({
15
+ id: faker.string.nanoid(25), // cuid()と同様の長さのIDを生成
16
+ name: faker.person.fullName(),
17
+ email: faker.internet.email(),
18
+ password: await getDefaultHashedPassword(),
19
+ status: UserStatus.active,
20
+ role: UserRole.user,
21
+ createdAt: randomDateInPastDays(30),
22
+ lastLogin: randomDateInPastDays(7),
23
+ emailVerified: null,
24
+ image: null,
25
+ }),
26
+ traits: {
27
+ active: {
28
+ data: {
29
+ status: UserStatus.active,
30
+ lastLogin: randomDateInPastDays(7),
31
+ },
32
+ },
33
+ inactive: {
34
+ data: {
35
+ status: UserStatus.inactive,
36
+ lastLogin: randomDateInPastDays(60),
37
+ },
38
+ },
39
+ pending: {
40
+ data: {
41
+ status: UserStatus.pending,
42
+ lastLogin: null,
43
+ emailVerified: null,
44
+ },
45
+ },
46
+ admin: {
47
+ data: {
48
+ role: UserRole.admin,
49
+ status: UserStatus.active,
50
+ emailVerified: new Date(),
51
+ },
52
+ },
53
+ moderator: {
54
+ data: {
55
+ role: UserRole.moderator,
56
+ status: UserStatus.active,
57
+ emailVerified: new Date(),
58
+ },
59
+ },
60
+ verified: {
61
+ data: {
62
+ emailVerified: new Date(),
63
+ status: UserStatus.active,
64
+ },
65
+ },
66
+ },
67
+ });
68
+
69
+ /**
70
+ * アクティブユーザーのファクトリー
71
+ */
72
+ export const ActiveUserFactory = UserFactory.use("active");
73
+
74
+ /**
75
+ * 非アクティブユーザーのファクトリー
76
+ */
77
+ export const InactiveUserFactory = UserFactory.use("inactive");
78
+
79
+ /**
80
+ * 保留中ユーザーのファクトリー
81
+ */
82
+ export const PendingUserFactory = UserFactory.use("pending");
83
+
84
+ /**
85
+ * 管理者ユーザーのファクトリー
86
+ */
87
+ export const AdminUserFactory = UserFactory.use("admin");
88
+
89
+ /**
90
+ * モデレーターユーザーのファクトリー
91
+ */
92
+ export const ModeratorUserFactory = UserFactory.use("moderator");
93
+
94
+ /**
95
+ * メール認証済みユーザーのファクトリー
96
+ */
97
+ export const VerifiedUserFactory = UserFactory.use("verified");
98
+
99
+ /**
100
+ * UserFactory.build()の結果をUserPropsに変換するヘルパー
101
+ * テストでUserエンティティを作成する際に使用
102
+ */
103
+ export async function buildUserProps(
104
+ overrides?: Partial<Prisma.UserCreateInput>,
105
+ ): Promise<UserProps> {
106
+ const data = await UserFactory.build(overrides);
107
+
108
+ // createdAtとlastLoginは文字列の可能性があるのでDate型に変換
109
+ const parseDate = (value: string | Date | null | undefined): Date | null => {
110
+ if (value == null) return null;
111
+ return typeof value === "string" ? new Date(value) : value;
112
+ };
113
+
114
+ return {
115
+ id: data.id ?? faker.string.nanoid(25),
116
+ email: data.email,
117
+ name: data.name ?? null,
118
+ status: data.status ?? "active",
119
+ role: data.role ?? "user",
120
+ createdAt: parseDate(data.createdAt) ?? new Date(),
121
+ lastLogin: parseDate(data.lastLogin),
122
+ };
123
+ }
@@ -0,0 +1,92 @@
1
+ import { type Prisma, UserRole, UserStatus } from "@prisma/client";
2
+
3
+ /**
4
+ * シードユーザーの型定義(Prisma.UserCreateInputから必要なフィールドを抽出)
5
+ */
6
+ export type SeedUser = Pick<
7
+ Prisma.UserCreateInput,
8
+ "name" | "email" | "status" | "role"
9
+ >;
10
+
11
+ /**
12
+ * シードデータ用の固定ユーザー定義
13
+ * 決定論的なデータで再現性を確保
14
+ */
15
+ export const SEED_USERS: readonly SeedUser[] = [
16
+ {
17
+ name: "田中太郎",
18
+ email: "tanaka@example.com",
19
+ status: UserStatus.active,
20
+ role: UserRole.admin,
21
+ },
22
+ {
23
+ name: "佐藤花子",
24
+ email: "sato@example.com",
25
+ status: UserStatus.active,
26
+ role: UserRole.user,
27
+ },
28
+ {
29
+ name: "鈴木一郎",
30
+ email: "suzuki@example.com",
31
+ status: UserStatus.inactive,
32
+ role: UserRole.user,
33
+ },
34
+ {
35
+ name: "高橋美咲",
36
+ email: "takahashi@example.com",
37
+ status: UserStatus.pending,
38
+ role: UserRole.moderator,
39
+ },
40
+ {
41
+ name: "伊藤健太",
42
+ email: "ito@example.com",
43
+ status: UserStatus.active,
44
+ role: UserRole.user,
45
+ },
46
+ {
47
+ name: "山田恵子",
48
+ email: "yamada@example.com",
49
+ status: UserStatus.active,
50
+ role: UserRole.admin,
51
+ },
52
+ {
53
+ name: "中村誠",
54
+ email: "nakamura@example.com",
55
+ status: UserStatus.inactive,
56
+ role: UserRole.user,
57
+ },
58
+ {
59
+ name: "小林優子",
60
+ email: "kobayashi@example.com",
61
+ status: UserStatus.pending,
62
+ role: UserRole.user,
63
+ },
64
+ ];
65
+
66
+ /**
67
+ * 管理者ユーザーのフィクスチャ
68
+ */
69
+ export const ADMIN_USERS = SEED_USERS.filter(
70
+ (user) => user.role === UserRole.admin,
71
+ );
72
+
73
+ /**
74
+ * アクティブユーザーのフィクスチャ
75
+ */
76
+ export const ACTIVE_USERS = SEED_USERS.filter(
77
+ (user) => user.status === UserStatus.active,
78
+ );
79
+
80
+ /**
81
+ * 非アクティブユーザーのフィクスチャ
82
+ */
83
+ export const INACTIVE_USERS = SEED_USERS.filter(
84
+ (user) => user.status === UserStatus.inactive,
85
+ );
86
+
87
+ /**
88
+ * 保留中ユーザーのフィクスチャ
89
+ */
90
+ export const PENDING_USERS = SEED_USERS.filter(
91
+ (user) => user.status === UserStatus.pending,
92
+ );
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ランダムな日付を生成(指定範囲内)
3
+ * @param start 開始日時
4
+ * @param end 終了日時
5
+ * @returns 範囲内のランダムな日時
6
+ */
7
+ export function randomDate(start: Date, end: Date): Date {
8
+ return new Date(
9
+ start.getTime() + Math.random() * (end.getTime() - start.getTime()),
10
+ );
11
+ }
12
+
13
+ /**
14
+ * 現在から指定日数前の日付を取得
15
+ * @param days 日数
16
+ * @returns 指定日数前の日付
17
+ */
18
+ export function daysAgo(days: number): Date {
19
+ const now = new Date();
20
+ return new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
21
+ }
22
+
23
+ /**
24
+ * 現在から指定日数後の日付を取得
25
+ * @param days 日数
26
+ * @returns 指定日数後の日付
27
+ */
28
+ export function daysFromNow(days: number): Date {
29
+ const now = new Date();
30
+ return new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
31
+ }
32
+
33
+ /**
34
+ * 過去N日間内のランダムな日付を生成
35
+ * @param days 日数
36
+ * @returns 過去N日間内のランダムな日付
37
+ */
38
+ export function randomDateInPastDays(days: number): Date {
39
+ return randomDate(daysAgo(days), new Date());
40
+ }
41
+
42
+ /**
43
+ * 未来N日間内のランダムな日付を生成
44
+ * @param days 日数
45
+ * @returns 未来N日間内のランダムな日付
46
+ */
47
+ export function randomDateInFutureDays(days: number): Date {
48
+ return randomDate(new Date(), daysFromNow(days));
49
+ }
@@ -0,0 +1,50 @@
1
+ import bcrypt from "bcryptjs";
2
+
3
+ /**
4
+ * パスワードをハッシュ化
5
+ * @param password プレーンテキストのパスワード
6
+ * @param saltRounds ソルトラウンド数(デフォルト: 10)
7
+ * @returns ハッシュ化されたパスワード
8
+ */
9
+ export async function hashPassword(
10
+ password: string,
11
+ saltRounds = 10,
12
+ ): Promise<string> {
13
+ const salt = await bcrypt.genSalt(saltRounds);
14
+ return bcrypt.hash(password, salt);
15
+ }
16
+
17
+ /**
18
+ * パスワードを検証
19
+ * @param password プレーンテキストのパスワード
20
+ * @param hashedPassword ハッシュ化されたパスワード
21
+ * @returns パスワードが一致する場合true
22
+ */
23
+ export async function verifyPassword(
24
+ password: string,
25
+ hashedPassword: string,
26
+ ): Promise<boolean> {
27
+ return bcrypt.compare(password, hashedPassword);
28
+ }
29
+
30
+ /**
31
+ * 開発環境用のデフォルトパスワード
32
+ */
33
+ export const DEFAULT_PASSWORD = "password123";
34
+
35
+ /**
36
+ * 開発環境用のデフォルトパスワード(ハッシュ化済み)
37
+ * テストやシードで使用するためのキャッシュ
38
+ */
39
+ let cachedHashedPassword: string | null = null;
40
+
41
+ /**
42
+ * デフォルトパスワードのハッシュを取得(キャッシュあり)
43
+ * @returns ハッシュ化されたデフォルトパスワード
44
+ */
45
+ export async function getDefaultHashedPassword(): Promise<string> {
46
+ if (!cachedHashedPassword) {
47
+ cachedHashedPassword = await hashPassword(DEFAULT_PASSWORD);
48
+ }
49
+ return cachedHashedPassword;
50
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * テストデータファクトリー - メインエクスポート
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { initialize, UserFactory } from "{{packageName}}/server-core/testing";
7
+ * import { prisma } from "{{packageName}}/server-core";
8
+ *
9
+ * // 初期化(テストセットアップ時)
10
+ * initialize({ prisma });
11
+ *
12
+ * // ユーザー作成
13
+ * const user = await UserFactory.create();
14
+ * const users = await UserFactory.createList(5);
15
+ * ```
16
+ */
17
+
18
+ // ファクトリー
19
+ export * from "./factories";
20
+
21
+ // フィクスチャ(シードデータ用)
22
+ export * from "./fixtures/users";
23
+
24
+ // ヘルパー関数
25
+ export * from "./helpers/date";
26
+ export * from "./helpers/password";
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "@repo/config/typescript",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": [
8
+ "src/**/*"
9
+ ],
10
+ "exclude": [
11
+ "node_modules",
12
+ "dist"
13
+ ]
14
+ }
@@ -0,0 +1,15 @@
1
+ import path from "node:path";
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ environment: "node",
8
+ include: ["src/**/*.test.ts"],
9
+ },
10
+ resolve: {
11
+ alias: {
12
+ "@": path.resolve(__dirname, "./src"),
13
+ },
14
+ },
15
+ });
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@repo/ui",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "exports": {
6
+ "./button": "./src/button.tsx",
7
+ "./card": "./src/card.tsx",
8
+ "./dialog": "./src/dialog.tsx",
9
+ "./input": "./src/input.tsx",
10
+ "./label": "./src/label.tsx",
11
+ "./table": "./src/table.tsx",
12
+ "./avatar": "./src/avatar.tsx",
13
+ "./badge": "./src/badge.tsx",
14
+ "./skeleton": "./src/skeleton.tsx",
15
+ "./separator": "./src/separator.tsx",
16
+ "./utils": "./src/lib/utils.ts"
17
+ },
18
+ "dependencies": {
19
+ "@radix-ui/react-avatar": "^1.1.10",
20
+ "@radix-ui/react-dialog": "^1.1.14",
21
+ "@radix-ui/react-label": "^2.1.7",
22
+ "@radix-ui/react-separator": "^1.1.7",
23
+ "@radix-ui/react-slot": "^1.2.3",
24
+ "class-variance-authority": "^0.7.1",
25
+ "clsx": "^2.1.1",
26
+ "lucide-react": "^0.523.0",
27
+ "react": "^19.0.0",
28
+ "react-dom": "^19.0.0",
29
+ "tailwind-merge": "^3.3.1"
30
+ },
31
+ "devDependencies": {
32
+ "@repo/config": "workspace:*",
33
+ "@types/react": "^19",
34
+ "@types/react-dom": "^19",
35
+ "typescript": "^5"
36
+ }
37
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
4
+ import { ChevronDownIcon } from "lucide-react";
5
+ import type * as React from "react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Accordion({
10
+ ...props
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
12
+ return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
13
+ }
14
+
15
+ function AccordionItem({
16
+ className,
17
+ ...props
18
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
19
+ return (
20
+ <AccordionPrimitive.Item
21
+ data-slot="accordion-item"
22
+ className={cn("border-b last:border-b-0", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ function AccordionTrigger({
29
+ className,
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
33
+ return (
34
+ <AccordionPrimitive.Header className="flex">
35
+ <AccordionPrimitive.Trigger
36
+ data-slot="accordion-trigger"
37
+ className={cn(
38
+ "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
39
+ className,
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
45
+ </AccordionPrimitive.Trigger>
46
+ </AccordionPrimitive.Header>
47
+ );
48
+ }
49
+
50
+ function AccordionContent({
51
+ className,
52
+ children,
53
+ ...props
54
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
55
+ return (
56
+ <AccordionPrimitive.Content
57
+ data-slot="accordion-content"
58
+ className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
59
+ {...props}
60
+ >
61
+ <div className={cn("pt-0 pb-4", className)}>{children}</div>
62
+ </AccordionPrimitive.Content>
63
+ );
64
+ }
65
+
66
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };