ui-syncup 0.3.13 → 0.4.0-beta.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 (1047) hide show
  1. package/.agents/skills/ai-spec-workflow/SKILL.md +58 -0
  2. package/.agents/skills/ai-spec-workflow/references/AI_SPECIFICATION_WORKFLOW.md +1434 -0
  3. package/.agents/skills/ai-spec-workflow/references/templates/design-template.md +729 -0
  4. package/.agents/skills/ai-spec-workflow/references/templates/requirements-template.md +179 -0
  5. package/.agents/skills/ai-spec-workflow/references/templates/tasks-template.md +501 -0
  6. package/.agents/skills/animation-designer/SKILL.md +688 -0
  7. package/.agents/skills/animation-designer/manifest.yaml +44 -0
  8. package/.agents/skills/brainstorming/SKILL.md +54 -0
  9. package/.agents/skills/contract-driven-ui/SKILL.md +270 -0
  10. package/.agents/skills/dispatching-parallel-agents/SKILL.md +180 -0
  11. package/.agents/skills/executing-plans/SKILL.md +76 -0
  12. package/.agents/skills/executing-specs/SKILL.md +53 -0
  13. package/.agents/skills/finishing-a-development-branch/SKILL.md +200 -0
  14. package/.agents/skills/github-workflow-automation/SKILL.md +846 -0
  15. package/.agents/skills/react-best-practices/AGENTS.md +2249 -0
  16. package/.agents/skills/react-best-practices/README.md +123 -0
  17. package/.agents/skills/react-best-practices/SKILL.md +121 -0
  18. package/.agents/skills/react-best-practices/metadata.json +15 -0
  19. package/.agents/skills/react-best-practices/rules/_sections.md +46 -0
  20. package/.agents/skills/react-best-practices/rules/_template.md +28 -0
  21. package/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  22. package/.agents/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  23. package/.agents/skills/react-best-practices/rules/async-api-routes.md +38 -0
  24. package/.agents/skills/react-best-practices/rules/async-defer-await.md +80 -0
  25. package/.agents/skills/react-best-practices/rules/async-dependencies.md +36 -0
  26. package/.agents/skills/react-best-practices/rules/async-parallel.md +28 -0
  27. package/.agents/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  28. package/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  29. package/.agents/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  30. package/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  31. package/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  32. package/.agents/skills/react-best-practices/rules/bundle-preload.md +50 -0
  33. package/.agents/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  34. package/.agents/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  35. package/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  36. package/.agents/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  37. package/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  38. package/.agents/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  39. package/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  40. package/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
  41. package/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  42. package/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
  43. package/.agents/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  44. package/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  45. package/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  46. package/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  47. package/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
  48. package/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  49. package/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  50. package/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  51. package/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  52. package/.agents/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  53. package/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  54. package/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  55. package/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  56. package/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  57. package/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  58. package/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  59. package/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
  60. package/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  61. package/.agents/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  62. package/.agents/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  63. package/.agents/skills/react-best-practices/rules/server-cache-react.md +26 -0
  64. package/.agents/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  65. package/.agents/skills/react-best-practices/rules/server-serialization.md +38 -0
  66. package/.agents/skills/react-ui-patterns/SKILL.md +289 -0
  67. package/.agents/skills/receiving-code-review/SKILL.md +213 -0
  68. package/.agents/skills/requesting-code-review/SKILL.md +105 -0
  69. package/.agents/skills/requesting-code-review/code-reviewer.md +146 -0
  70. package/.agents/skills/reviewing-code/SKILL.md +28 -0
  71. package/.agents/skills/shadcn/SKILL.md +240 -0
  72. package/.agents/skills/shadcn/agents/openai.yml +5 -0
  73. package/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  74. package/.agents/skills/shadcn/assets/shadcn.png +0 -0
  75. package/.agents/skills/shadcn/cli.md +255 -0
  76. package/.agents/skills/shadcn/customization.md +202 -0
  77. package/.agents/skills/shadcn/evals/evals.json +47 -0
  78. package/.agents/skills/shadcn/mcp.md +94 -0
  79. package/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  80. package/.agents/skills/shadcn/rules/composition.md +195 -0
  81. package/.agents/skills/shadcn/rules/forms.md +192 -0
  82. package/.agents/skills/shadcn/rules/icons.md +101 -0
  83. package/.agents/skills/shadcn/rules/styling.md +162 -0
  84. package/.agents/skills/steering-creation/SKILL.md +221 -0
  85. package/.agents/skills/steering-creation/references/STEERING_CREATION_INSTRUCTION.md +850 -0
  86. package/.agents/skills/subagent-driven-development/SKILL.md +240 -0
  87. package/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
  88. package/.agents/skills/subagent-driven-development/implementer-prompt.md +78 -0
  89. package/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  90. package/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -0
  91. package/.agents/skills/systematic-debugging/SKILL.md +296 -0
  92. package/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  93. package/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -0
  94. package/.agents/skills/systematic-debugging/defense-in-depth.md +122 -0
  95. package/.agents/skills/systematic-debugging/find-polluter.sh +63 -0
  96. package/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -0
  97. package/.agents/skills/systematic-debugging/test-academic.md +14 -0
  98. package/.agents/skills/systematic-debugging/test-pressure-1.md +58 -0
  99. package/.agents/skills/systematic-debugging/test-pressure-2.md +68 -0
  100. package/.agents/skills/systematic-debugging/test-pressure-3.md +69 -0
  101. package/.agents/skills/test-driven-development/SKILL.md +371 -0
  102. package/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
  103. package/.agents/skills/using-git-worktrees/SKILL.md +217 -0
  104. package/.agents/skills/using-superpowers/SKILL.md +87 -0
  105. package/.agents/skills/verification-before-completion/SKILL.md +139 -0
  106. package/.agents/skills/web-design-guidelines/SKILL.md +36 -0
  107. package/.agents/skills/writing-plans/SKILL.md +116 -0
  108. package/.agents/skills/writing-skills/SKILL.md +655 -0
  109. package/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
  110. package/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  111. package/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
  112. package/.agents/skills/writing-skills/persuasion-principles.md +187 -0
  113. package/.agents/skills/writing-skills/render-graphs.js +168 -0
  114. package/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  115. package/.ai/steering/product.md +51 -0
  116. package/.ai/steering/structure.md +275 -0
  117. package/.ai/steering/tech.md +188 -0
  118. package/.claude/agents/database-architect.md +96 -0
  119. package/.claude/agents/deployment-pipeline-architect.md +122 -0
  120. package/.claude/agents/nextjs-expert.md +69 -0
  121. package/.claude/agents/ui-design-expert.md +106 -0
  122. package/.claudeignore +69 -0
  123. package/.dockerignore +8 -0
  124. package/.env.development +86 -0
  125. package/.env.example +171 -0
  126. package/.env.production +139 -0
  127. package/.env.test +58 -0
  128. package/.gitattributes +2 -0
  129. package/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
  130. package/.github/ISSUE_TEMPLATE/feature_request.yml +20 -0
  131. package/.github/PULL_REQUEST_TEMPLATE.md +23 -0
  132. package/.github/workflows/ci.yml +64 -0
  133. package/.github/workflows/release.yml +131 -0
  134. package/.nvmrc +1 -0
  135. package/.releaserc.json +18 -0
  136. package/.vercelignore +73 -0
  137. package/AGENTS.md +544 -0
  138. package/CHANGELOG.md +37 -0
  139. package/CODE_OF_CONDUCT.md +21 -0
  140. package/CONTRIBUTING.md +32 -0
  141. package/Dockerfile +84 -0
  142. package/LICENSE +21 -0
  143. package/README.md +328 -59
  144. package/SECURITY.md +16 -0
  145. package/bun.lock +3853 -0
  146. package/cli/README.md +94 -0
  147. package/cli/bun.lock +306 -0
  148. package/cli/index.ts +96 -0
  149. package/cli/package-lock.json +2157 -0
  150. package/cli/package.json +30 -0
  151. package/cli/src/commands/backup.ts +78 -0
  152. package/cli/src/commands/doctor.ts +82 -0
  153. package/cli/src/commands/init.ts +234 -0
  154. package/cli/src/commands/logs.ts +26 -0
  155. package/cli/src/commands/open.ts +23 -0
  156. package/cli/src/commands/remove.ts +44 -0
  157. package/cli/src/commands/restart.ts +21 -0
  158. package/cli/src/commands/restore.ts +90 -0
  159. package/cli/src/commands/start.ts +26 -0
  160. package/cli/src/commands/status.ts +25 -0
  161. package/cli/src/commands/stop.ts +20 -0
  162. package/cli/src/commands/upgrade.ts +28 -0
  163. package/cli/src/lib/docker.ts +40 -0
  164. package/cli/src/lib/env.ts +42 -0
  165. package/cli/src/lib/ui.ts +43 -0
  166. package/cli/tsconfig.json +13 -0
  167. package/cli/tsup.config.ts +12 -0
  168. package/components.json +24 -0
  169. package/docker/README.md +430 -0
  170. package/docker/compose.dev-minio.yml +30 -0
  171. package/docker/compose.dev.yml +39 -0
  172. package/docker/compose.local.yml +84 -0
  173. package/docker/compose.yml +153 -0
  174. package/docs/VERSIONING.md +117 -0
  175. package/docs/database/DRIZZLE_COMMANDS_EXPLAINED.md +1779 -0
  176. package/docs/database/DRIZZLE_ZOD_POSTGRESQL_INSTRUCTION.md +646 -0
  177. package/docs/database/MIGRATION_BEST_PRACTICES.md +601 -0
  178. package/docs/database/MIGRATION_ROLLBACK.md +1080 -0
  179. package/docs/database/MIGRATION_SYSTEM.md +165 -0
  180. package/docs/database/MIGRATION_TROUBLESHOOTING.md +881 -0
  181. package/docs/development/ENVIRONMENT_CONFIG.md +896 -0
  182. package/docs/development/LOCAL_DEVELOPMENT.md +456 -0
  183. package/docs/development/REMOTE_DATABASE_SETUP.md +786 -0
  184. package/docs/development/STORAGE_SETUP.md +207 -0
  185. package/docs/development/SUPABASE_LOCAL_SETUP.md +178 -0
  186. package/docs/development/TESTING.md +714 -0
  187. package/docs/feature-architectures/LOADING_ARCHITECTURE.md +343 -0
  188. package/docs/feature-architectures/NOTIFICATION_ARCHITECTURE.md +858 -0
  189. package/docs/feature-architectures/RATE_LIMIT_RESET.md +147 -0
  190. package/docs/feature-architectures/RBAC.md +1132 -0
  191. package/docs/feature-architectures/RESOURCE_LIMITS.md +69 -0
  192. package/docs/feature-architectures/SECURITY.md +284 -0
  193. package/docs/feature-architectures/WORKSPACES.md +278 -0
  194. package/docs/plans/admin-setup-wizard-routing-plan.md +623 -0
  195. package/drizzle/0000_purple_wilson_fisk.sql +360 -0
  196. package/drizzle/0001_drop_instance_public_url.sql +1 -0
  197. package/drizzle/meta/0000_snapshot.json +3118 -0
  198. package/drizzle/meta/_journal.json +20 -0
  199. package/drizzle.config.ts +13 -0
  200. package/eslint.config.mjs +44 -0
  201. package/install.sh +180 -0
  202. package/next.config.ts +91 -0
  203. package/package.json +128 -22
  204. package/playwright.config.ts +70 -0
  205. package/postcss.config.mjs +7 -0
  206. package/public/file.svg +1 -0
  207. package/public/globe.svg +1 -0
  208. package/public/logo.svg +11 -0
  209. package/public/next.svg +1 -0
  210. package/public/playground/CPM-101/as-is-image.jpg +0 -0
  211. package/public/playground/CPM-101/to-be-image.jpg +0 -0
  212. package/public/playground/TEST-1/LinkedIn-skeleton-screen.png +0 -0
  213. package/public/playground/TEST-1/https___dev-to-uploads.s3.amazonaws.com_uploads_articles_vuahe90ka1mkx9aepmea.webp +0 -0
  214. package/public/playground/TEST-1/linkedin_skeletonscreen.jpg +0 -0
  215. package/public/vercel.svg +1 -0
  216. package/public/window.svg +1 -0
  217. package/scripts/__tests__/migrate.integration.test.ts +642 -0
  218. package/scripts/__tests__/migrate.property.test.ts +1714 -0
  219. package/scripts/__tests__/migrate.test.ts +536 -0
  220. package/scripts/admin-reset-password.ts +114 -0
  221. package/scripts/check-email-queue.ts +99 -0
  222. package/scripts/check-sessions.ts +50 -0
  223. package/scripts/db-pull-data.sh +73 -0
  224. package/scripts/force-verify-email.sh +13 -0
  225. package/scripts/migrate.ts +693 -0
  226. package/scripts/process-email-queue.ts +26 -0
  227. package/scripts/reset-db.ts +47 -0
  228. package/scripts/reset-rate-limit.sh +26 -0
  229. package/scripts/reset-remote-db.sql +31 -0
  230. package/scripts/retry-failed-emails.ts +67 -0
  231. package/scripts/seed.ts +605 -0
  232. package/scripts/setup-monitoring.sh +440 -0
  233. package/scripts/sync-migration-tracking.ts +113 -0
  234. package/scripts/test-ci-error-handling.sh +237 -0
  235. package/scripts/test-ci-workflow.sh +200 -0
  236. package/scripts/test-migration.sh +151 -0
  237. package/scripts/validate-env.ts +25 -0
  238. package/scripts/validate-migration-system.ts +566 -0
  239. package/scripts/verify-ci-status-reporting.sh +206 -0
  240. package/scripts/verify-user-email.sql +22 -0
  241. package/scripts/verify-vercel-integration.ts +292 -0
  242. package/seed_data.md +54 -0
  243. package/src/app/(protected)/(team)/(routes)/[projectSlug]/error.tsx +89 -0
  244. package/src/app/(protected)/(team)/(routes)/[projectSlug]/loading.tsx +101 -0
  245. package/src/app/(protected)/(team)/(routes)/[projectSlug]/page.tsx +91 -0
  246. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/README.md +192 -0
  247. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/error.tsx +58 -0
  248. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/loading.tsx +14 -0
  249. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/not-found.tsx +47 -0
  250. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/page.tsx +91 -0
  251. package/src/app/(protected)/(team)/projects/page.tsx +16 -0
  252. package/src/app/(protected)/(team)/team/settings/(section)/instance/page.tsx +52 -0
  253. package/src/app/(protected)/(team)/team/settings/(section)/integrations/loading.tsx +5 -0
  254. package/src/app/(protected)/(team)/team/settings/(section)/integrations/page.tsx +23 -0
  255. package/src/app/(protected)/(team)/team/settings/(section)/members/loading.tsx +5 -0
  256. package/src/app/(protected)/(team)/team/settings/(section)/members/page.tsx +35 -0
  257. package/src/app/(protected)/(team)/team/settings/layout.tsx +72 -0
  258. package/src/app/(protected)/(team)/team/settings/loading.tsx +5 -0
  259. package/src/app/(protected)/(team)/team/settings/page.tsx +71 -0
  260. package/src/app/(protected)/dev/auth/README.md +151 -0
  261. package/src/app/(protected)/dev/auth/page.tsx +590 -0
  262. package/src/app/(protected)/layout.test.tsx +209 -0
  263. package/src/app/(protected)/layout.tsx +28 -0
  264. package/src/app/(protected)/onboarding/page.tsx +27 -0
  265. package/src/app/(protected)/settings/integrations/page.tsx +23 -0
  266. package/src/app/(protected)/settings/layout.tsx +26 -0
  267. package/src/app/(protected)/settings/notifications/page.tsx +26 -0
  268. package/src/app/(protected)/settings/other/page.tsx +23 -0
  269. package/src/app/(protected)/settings/page.tsx +23 -0
  270. package/src/app/(protected)/settings/preferences/page.tsx +23 -0
  271. package/src/app/(protected)/settings/security/page.tsx +37 -0
  272. package/src/app/(public)/forgot-password/page.tsx +20 -0
  273. package/src/app/(public)/invite/project/[token]/error.tsx +50 -0
  274. package/src/app/(public)/invite/project/[token]/loading.tsx +39 -0
  275. package/src/app/(public)/invite/project/[token]/page.tsx +156 -0
  276. package/src/app/(public)/layout.tsx +9 -0
  277. package/src/app/(public)/privacy-policy/page.tsx +12 -0
  278. package/src/app/(public)/reset-password/page.tsx +37 -0
  279. package/src/app/(public)/setup/__tests__/page.test.tsx +30 -0
  280. package/src/app/(public)/setup/page.tsx +17 -0
  281. package/src/app/(public)/share/issue/[token]/page.tsx +51 -0
  282. package/src/app/(public)/sign-in/page.tsx +55 -0
  283. package/src/app/(public)/sign-up/page.tsx +23 -0
  284. package/src/app/(public)/verify-email/page.tsx +22 -0
  285. package/src/app/(public)/verify-email-confirm/page.tsx +40 -0
  286. package/src/app/api/auth/[...all]/route.ts +6 -0
  287. package/src/app/api/auth/delete-account/route.ts +134 -0
  288. package/src/app/api/auth/dev/force-verify/route.ts +180 -0
  289. package/src/app/api/auth/dev/reset-rate-limit/route.ts +144 -0
  290. package/src/app/api/auth/dev/sessions/route.ts +172 -0
  291. package/src/app/api/auth/forgot-password/__tests__/forgot-password.property.test.ts +397 -0
  292. package/src/app/api/auth/forgot-password/route.ts +277 -0
  293. package/src/app/api/auth/logout/route.ts +115 -0
  294. package/src/app/api/auth/me/route.ts +123 -0
  295. package/src/app/api/auth/providers/__tests__/route.test.ts +236 -0
  296. package/src/app/api/auth/providers/route.ts +119 -0
  297. package/src/app/api/auth/resend-verification/route.ts +262 -0
  298. package/src/app/api/auth/reset-password/__tests__/reset-password.property.test.ts +493 -0
  299. package/src/app/api/auth/reset-password/__tests__/route.test.ts +284 -0
  300. package/src/app/api/auth/reset-password/route.ts +251 -0
  301. package/src/app/api/auth/verify-email/route.ts +232 -0
  302. package/src/app/api/example-cors/route.ts +61 -0
  303. package/src/app/api/health/route.ts +14 -0
  304. package/src/app/api/invite/project/[token]/__tests__/accept-invitation.integration.test.ts +348 -0
  305. package/src/app/api/invite/project/[token]/decline/route.ts +99 -0
  306. package/src/app/api/invite/project/[token]/route.ts +269 -0
  307. package/src/app/api/issues/[issueId]/activities/route.ts +213 -0
  308. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/[commentId]/route.ts +486 -0
  309. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/route.ts +283 -0
  310. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/read/route.ts +242 -0
  311. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/route.ts +534 -0
  312. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/route.ts +514 -0
  313. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/route.ts +161 -0
  314. package/src/app/api/issues/[issueId]/attachments/route.ts +376 -0
  315. package/src/app/api/issues/[issueId]/route.ts +516 -0
  316. package/src/app/api/notifications/[id]/read/route.ts +131 -0
  317. package/src/app/api/notifications/__tests__/notifications.integration.test.ts +350 -0
  318. package/src/app/api/notifications/read-all/route.ts +72 -0
  319. package/src/app/api/notifications/route.ts +148 -0
  320. package/src/app/api/notifications/unread-count/route.ts +77 -0
  321. package/src/app/api/projects/[id]/activities/route.ts +174 -0
  322. package/src/app/api/projects/[id]/invitations/[invitationId]/resend/route.ts +99 -0
  323. package/src/app/api/projects/[id]/invitations/[invitationId]/route.ts +96 -0
  324. package/src/app/api/projects/[id]/invitations/route.ts +254 -0
  325. package/src/app/api/projects/[id]/issues/route.ts +452 -0
  326. package/src/app/api/projects/[id]/join/route.ts +207 -0
  327. package/src/app/api/projects/[id]/members/[memberId]/route.ts +364 -0
  328. package/src/app/api/projects/[id]/members/me/route.ts +121 -0
  329. package/src/app/api/projects/[id]/members/route.ts +129 -0
  330. package/src/app/api/projects/[id]/route.ts +476 -0
  331. package/src/app/api/projects/route.ts +394 -0
  332. package/src/app/api/setup/admin/route.ts +255 -0
  333. package/src/app/api/setup/complete/__tests__/route.test.ts +60 -0
  334. package/src/app/api/setup/complete/route.ts +244 -0
  335. package/src/app/api/setup/config/route.ts +195 -0
  336. package/src/app/api/setup/export/route.ts +111 -0
  337. package/src/app/api/setup/health/route.ts +74 -0
  338. package/src/app/api/setup/import/route.ts +154 -0
  339. package/src/app/api/setup/status/route.ts +82 -0
  340. package/src/app/api/setup/workspace/route.ts +252 -0
  341. package/src/app/api/teams/[teamId]/export/route.ts +115 -0
  342. package/src/app/api/teams/[teamId]/invitations/[invitationId]/resend/route.ts +132 -0
  343. package/src/app/api/teams/[teamId]/invitations/[invitationId]/route.ts +117 -0
  344. package/src/app/api/teams/[teamId]/invitations/route.ts +363 -0
  345. package/src/app/api/teams/[teamId]/members/[userId]/route.ts +335 -0
  346. package/src/app/api/teams/[teamId]/members/route.ts +184 -0
  347. package/src/app/api/teams/[teamId]/members/search/route.ts +202 -0
  348. package/src/app/api/teams/[teamId]/route.ts +423 -0
  349. package/src/app/api/teams/[teamId]/switch/route.ts +140 -0
  350. package/src/app/api/teams/[teamId]/transfer-ownership/route.ts +212 -0
  351. package/src/app/api/teams/invitations/[token]/accept/route.ts +140 -0
  352. package/src/app/api/teams/invitations/by-id/[id]/accept/route.ts +98 -0
  353. package/src/app/api/teams/invitations/by-id/[id]/decline/route.ts +90 -0
  354. package/src/app/api/teams/route.ts +278 -0
  355. package/src/app/api/uploads/media/route.ts +118 -0
  356. package/src/app/api/uploads/presigned/route.ts +49 -0
  357. package/src/app/api/user/linked-accounts/route.ts +35 -0
  358. package/src/app/email-preview/page.tsx +11 -0
  359. package/src/app/favicon.ico +0 -0
  360. package/src/app/global-error.tsx +21 -0
  361. package/src/app/layout.tsx +50 -0
  362. package/src/app/page.tsx +5 -0
  363. package/src/components/icons/atlassian-icon.tsx +22 -0
  364. package/src/components/icons/index.ts +1 -0
  365. package/src/components/layout/SIDEBAR_LAYOUT_BEST_PRACTICES.md +240 -0
  366. package/src/components/layout/app-shell-header-store.tsx +20 -0
  367. package/src/components/layout/app-shell-skeleton.tsx +89 -0
  368. package/src/components/layout/app-shell-wrapper.tsx +32 -0
  369. package/src/components/layout/app-shell.test.tsx +155 -0
  370. package/src/components/layout/app-shell.tsx +100 -0
  371. package/src/components/shared/headers/app-header-configurator.tsx +42 -0
  372. package/src/components/shared/headers/app-header.tsx +103 -0
  373. package/src/components/shared/headers/header-user-menu.tsx +247 -0
  374. package/src/components/shared/headers/index.ts +44 -0
  375. package/src/components/shared/headers/page-header.tsx +25 -0
  376. package/src/components/shared/notifications/__tests__/notification-bell.test.tsx +159 -0
  377. package/src/components/shared/notifications/__tests__/notification-dropdown.test.tsx +296 -0
  378. package/src/components/shared/notifications/__tests__/notification-item.test.tsx +328 -0
  379. package/src/components/shared/notifications/index.ts +45 -0
  380. package/src/components/shared/notifications/notification-actions.tsx +295 -0
  381. package/src/components/shared/notifications/notification-bell-button.tsx +77 -0
  382. package/src/components/shared/notifications/notification-dropdown.tsx +160 -0
  383. package/src/components/shared/notifications/notification-group-item.tsx +268 -0
  384. package/src/components/shared/notifications/notification-item.tsx +193 -0
  385. package/src/components/shared/notifications/notification-load-more.tsx +50 -0
  386. package/src/components/shared/notifications/notification-panel.tsx +49 -0
  387. package/src/components/shared/notifications/utils.tsx +127 -0
  388. package/src/components/shared/permission-guard/index.ts +1 -0
  389. package/src/components/shared/permission-guard/permission-tooltip.tsx +45 -0
  390. package/src/components/shared/relative-time.tsx +53 -0
  391. package/src/components/shared/section-container.tsx +32 -0
  392. package/src/components/shared/service-status-banner.tsx +121 -0
  393. package/src/components/shared/settings-sidebar/index.ts +2 -0
  394. package/src/components/shared/settings-sidebar/team-setting-aside.tsx +97 -0
  395. package/src/components/shared/settings-sidebar/user-settings-aside.tsx +66 -0
  396. package/src/components/shared/sidebar/app-sidebar.tsx +146 -0
  397. package/src/components/shared/sidebar/index.ts +36 -0
  398. package/src/components/shared/sidebar/sidebar-main.tsx +81 -0
  399. package/src/components/shared/sidebar/sidebar-project.tsx +61 -0
  400. package/src/components/shared/sidebar/sidebar-team-avatar.tsx +126 -0
  401. package/src/components/shared/sidebar/sidebar-team-switcher.tsx +185 -0
  402. package/src/components/shared/sidebar/type.ts +97 -0
  403. package/src/components/ui/alert-dialog.tsx +157 -0
  404. package/src/components/ui/alert.tsx +66 -0
  405. package/src/components/ui/avatar-upload.tsx +147 -0
  406. package/src/components/ui/avatar.tsx +53 -0
  407. package/src/components/ui/badge.tsx +46 -0
  408. package/src/components/ui/breadcrumb.tsx +109 -0
  409. package/src/components/ui/button.tsx +60 -0
  410. package/src/components/ui/card.tsx +92 -0
  411. package/src/components/ui/checkbox.tsx +32 -0
  412. package/src/components/ui/collapsible.tsx +33 -0
  413. package/src/components/ui/command.tsx +184 -0
  414. package/src/components/ui/dialog.tsx +143 -0
  415. package/src/components/ui/dropdown-menu.tsx +257 -0
  416. package/src/components/ui/empty.tsx +104 -0
  417. package/src/components/ui/field.tsx +244 -0
  418. package/src/components/ui/image-cropper-dialog.tsx +167 -0
  419. package/src/components/ui/input.tsx +21 -0
  420. package/src/components/ui/label.tsx +24 -0
  421. package/src/components/ui/optimized-image.tsx +220 -0
  422. package/src/components/ui/pagination.tsx +127 -0
  423. package/src/components/ui/popover.tsx +48 -0
  424. package/src/components/ui/progress.tsx +31 -0
  425. package/src/components/ui/radio-group.tsx +45 -0
  426. package/src/components/ui/scroll-area.tsx +58 -0
  427. package/src/components/ui/select.tsx +187 -0
  428. package/src/components/ui/separator.tsx +28 -0
  429. package/src/components/ui/sheet.tsx +139 -0
  430. package/src/components/ui/sidebar.tsx +733 -0
  431. package/src/components/ui/skeleton.tsx +13 -0
  432. package/src/components/ui/sonner.tsx +40 -0
  433. package/src/components/ui/spinner.tsx +16 -0
  434. package/src/components/ui/switch.tsx +31 -0
  435. package/src/components/ui/table.tsx +116 -0
  436. package/src/components/ui/tabs.tsx +66 -0
  437. package/src/components/ui/textarea.tsx +23 -0
  438. package/src/components/ui/tooltip.tsx +61 -0
  439. package/src/config/__tests__/workspace.property.test.ts +40 -0
  440. package/src/config/auth.ts +62 -0
  441. package/src/config/integrations.ts +126 -0
  442. package/src/config/quotas.ts +20 -0
  443. package/src/config/roles.ts +463 -0
  444. package/src/config/settings-nav.ts +39 -0
  445. package/src/config/team-settings-nav.ts +37 -0
  446. package/src/config/user-settings-nav.ts +42 -0
  447. package/src/config/version.ts +1 -0
  448. package/src/config/workspace.ts +64 -0
  449. package/src/features/annotations/README.md +283 -0
  450. package/src/features/annotations/api/annotations-api.ts +194 -0
  451. package/src/features/annotations/api/comments-api.ts +147 -0
  452. package/src/features/annotations/api/index.ts +71 -0
  453. package/src/features/annotations/api/save-annotation.ts +150 -0
  454. package/src/features/annotations/api/schemas.ts +142 -0
  455. package/src/features/annotations/components/annotated-attachment-view.tsx +576 -0
  456. package/src/features/annotations/components/annotation-action-sheet.tsx +140 -0
  457. package/src/features/annotations/components/annotation-annotations-panel.tsx +213 -0
  458. package/src/features/annotations/components/annotation-box.tsx +539 -0
  459. package/src/features/annotations/components/annotation-canvas.tsx +534 -0
  460. package/src/features/annotations/components/annotation-comment-input.tsx +145 -0
  461. package/src/features/annotations/components/annotation-context-menu.tsx +164 -0
  462. package/src/features/annotations/components/annotation-drawer.tsx +231 -0
  463. package/src/features/annotations/components/annotation-layer.tsx +271 -0
  464. package/src/features/annotations/components/annotation-pin.tsx +318 -0
  465. package/src/features/annotations/components/annotation-popover.tsx +562 -0
  466. package/src/features/annotations/components/annotation-thread-panel.tsx +485 -0
  467. package/src/features/annotations/components/annotation-thread-preview.tsx +195 -0
  468. package/src/features/annotations/components/annotation-toolbar.tsx +244 -0
  469. package/src/features/annotations/components/keyboard-shortcuts-modal.tsx +79 -0
  470. package/src/features/annotations/docs/ANNOTATIONS_ARCHITECTURE.md +67 -0
  471. package/src/features/annotations/docs/ANNOTATION_SAVE_ARCHITECTURE.md +422 -0
  472. package/src/features/annotations/docs/ANNOTATION_SAVE_FEATURE.md +408 -0
  473. package/src/features/annotations/docs/BOX_ANNOTATION_GUIDE.md +542 -0
  474. package/src/features/annotations/docs/NEXTSTEP.md +28 -0
  475. package/src/features/annotations/docs/STALE_CLOSURE_FIX.md +344 -0
  476. package/src/features/annotations/docs/UNDO_REDO_QUICK_START.md +545 -0
  477. package/src/features/annotations/docs/local_first_canvas_autosave_architecture.md +674 -0
  478. package/src/features/annotations/examples/complete-example.tsx +266 -0
  479. package/src/features/annotations/examples/save-annotation-example.tsx +309 -0
  480. package/src/features/annotations/hooks/__tests__/use-annotation-permissions.property.test.tsx +493 -0
  481. package/src/features/annotations/hooks/index.ts +36 -0
  482. package/src/features/annotations/hooks/use-annotation-batch-save.ts +109 -0
  483. package/src/features/annotations/hooks/use-annotation-comments.ts +353 -0
  484. package/src/features/annotations/hooks/use-annotation-drafts.ts +137 -0
  485. package/src/features/annotations/hooks/use-annotation-edit-state.ts +99 -0
  486. package/src/features/annotations/hooks/use-annotation-history-tracker.ts +159 -0
  487. package/src/features/annotations/hooks/use-annotation-integration.ts +916 -0
  488. package/src/features/annotations/hooks/use-annotation-permissions.ts +210 -0
  489. package/src/features/annotations/hooks/use-annotation-popover.ts +175 -0
  490. package/src/features/annotations/hooks/use-annotation-save.ts +208 -0
  491. package/src/features/annotations/hooks/use-annotation-tools.ts +237 -0
  492. package/src/features/annotations/hooks/use-annotations-with-history.ts +332 -0
  493. package/src/features/annotations/hooks/use-auto-save.ts +94 -0
  494. package/src/features/annotations/index.ts +111 -0
  495. package/src/features/annotations/types/annotation.ts +201 -0
  496. package/src/features/annotations/types/index.ts +28 -0
  497. package/src/features/annotations/utils/history-manager.ts +73 -0
  498. package/src/features/annotations/utils/index.ts +2 -0
  499. package/src/features/annotations/utils/map-attachments-to-threads.ts +28 -0
  500. package/src/features/annotations/utils/position-comment-input.ts +136 -0
  501. package/src/features/annotations/utils/re-sequence-labels.ts +92 -0
  502. package/src/features/annotations/utils/validate-annotation-label.ts +120 -0
  503. package/src/features/auth/api/types.ts +101 -0
  504. package/src/features/auth/components/__tests__/role-gate.test.tsx +448 -0
  505. package/src/features/auth/components/__tests__/social-login-buttons.test.tsx +313 -0
  506. package/src/features/auth/components/auth-card.tsx +36 -0
  507. package/src/features/auth/components/forgot-password-form.tsx +115 -0
  508. package/src/features/auth/components/index.ts +14 -0
  509. package/src/features/auth/components/invite-code-input.tsx +155 -0
  510. package/src/features/auth/components/invited-user-form.tsx +309 -0
  511. package/src/features/auth/components/onboarding-form.tsx +195 -0
  512. package/src/features/auth/components/password-strength-indicator.tsx +113 -0
  513. package/src/features/auth/components/reset-password-form.tsx +138 -0
  514. package/src/features/auth/components/role-gate.tsx +124 -0
  515. package/src/features/auth/components/self-registration-choice.tsx +153 -0
  516. package/src/features/auth/components/sign-in-form.tsx +159 -0
  517. package/src/features/auth/components/sign-up-form.tsx +158 -0
  518. package/src/features/auth/components/social-login-buttons.tsx +219 -0
  519. package/src/features/auth/hooks/__tests__/use-onboarding.test.tsx +109 -0
  520. package/src/features/auth/hooks/__tests__/use-session.test.tsx +160 -0
  521. package/src/features/auth/hooks/index.ts +15 -0
  522. package/src/features/auth/hooks/use-accept-invitation.ts +194 -0
  523. package/src/features/auth/hooks/use-delete-account.ts +86 -0
  524. package/src/features/auth/hooks/use-force-verify.ts +89 -0
  525. package/src/features/auth/hooks/use-forgot-password.ts +144 -0
  526. package/src/features/auth/hooks/use-link-account.ts +78 -0
  527. package/src/features/auth/hooks/use-linked-accounts.ts +88 -0
  528. package/src/features/auth/hooks/use-onboarding.ts +159 -0
  529. package/src/features/auth/hooks/use-resend-verification.ts +139 -0
  530. package/src/features/auth/hooks/use-reset-password.ts +151 -0
  531. package/src/features/auth/hooks/use-reset-rate-limit.ts +56 -0
  532. package/src/features/auth/hooks/use-self-registration.ts +202 -0
  533. package/src/features/auth/hooks/use-session.ts +81 -0
  534. package/src/features/auth/hooks/use-sessions.ts +59 -0
  535. package/src/features/auth/hooks/use-sign-in.ts +234 -0
  536. package/src/features/auth/hooks/use-sign-out.ts +88 -0
  537. package/src/features/auth/hooks/use-sign-up.ts +194 -0
  538. package/src/features/auth/hooks/use-unlink-account.ts +100 -0
  539. package/src/features/auth/hooks/use-verify-email-token.ts +125 -0
  540. package/src/features/auth/index.ts +75 -0
  541. package/src/features/auth/screens/forgot-password-screen.tsx +33 -0
  542. package/src/features/auth/screens/index.ts +7 -0
  543. package/src/features/auth/screens/onboarding-screen.tsx +49 -0
  544. package/src/features/auth/screens/reset-password-screen.tsx +33 -0
  545. package/src/features/auth/screens/sign-in-screen.tsx +61 -0
  546. package/src/features/auth/screens/sign-up-screen.tsx +37 -0
  547. package/src/features/auth/screens/verify-email-confirm-screen.tsx +286 -0
  548. package/src/features/auth/screens/verify-email-screen.tsx +146 -0
  549. package/src/features/auth/types/index.ts +14 -0
  550. package/src/features/auth/utils/__tests__/validators.test.ts +331 -0
  551. package/src/features/auth/utils/password-strength.ts +129 -0
  552. package/src/features/auth/utils/validators.ts +124 -0
  553. package/src/features/email-preview/actions/render-email.ts +21 -0
  554. package/src/features/email-preview/screens/email-preview-screen.tsx +81 -0
  555. package/src/features/folder-scaffold-template/index.ts +0 -0
  556. package/src/features/instance-settings/components/index.ts +6 -0
  557. package/src/features/instance-settings/components/instance-settings-form.tsx +180 -0
  558. package/src/features/instance-settings/components/instance-status-display.tsx +158 -0
  559. package/src/features/instance-settings/index.ts +7 -0
  560. package/src/features/instance-settings/screens/index.ts +5 -0
  561. package/src/features/instance-settings/screens/instance-settings-screen.tsx +59 -0
  562. package/src/features/issues/README.md +330 -0
  563. package/src/features/issues/api/create-issue.ts +19 -0
  564. package/src/features/issues/api/delete-issue.ts +27 -0
  565. package/src/features/issues/api/get-issue-activities.ts +58 -0
  566. package/src/features/issues/api/get-issue-details.ts +25 -0
  567. package/src/features/issues/api/get-project-issues-server.ts +44 -0
  568. package/src/features/issues/api/get-project-issues.ts +21 -0
  569. package/src/features/issues/api/index.ts +44 -0
  570. package/src/features/issues/api/update-issue.ts +31 -0
  571. package/src/features/issues/api/upload-attachment.ts +81 -0
  572. package/src/features/issues/components/activity-timeline.tsx +440 -0
  573. package/src/features/issues/components/canvas-state-indicator.tsx +90 -0
  574. package/src/features/issues/components/centered-canvas-view.tsx +739 -0
  575. package/src/features/issues/components/image-selector.tsx +123 -0
  576. package/src/features/issues/components/image-upload-zone.tsx +262 -0
  577. package/src/features/issues/components/infinite-canvas-background.tsx +163 -0
  578. package/src/features/issues/components/inline-editable-select.tsx +173 -0
  579. package/src/features/issues/components/inline-editable-text.tsx +225 -0
  580. package/src/features/issues/components/inline-editable-textarea.tsx +219 -0
  581. package/src/features/issues/components/inline-editable-user-select.tsx +202 -0
  582. package/src/features/issues/components/issue-deletion-dialog.tsx +142 -0
  583. package/src/features/issues/components/issue-details-panel.tsx +101 -0
  584. package/src/features/issues/components/issues-create-dialog.tsx +578 -0
  585. package/src/features/issues/components/issues-list-filter.tsx +312 -0
  586. package/src/features/issues/components/issues-list.tsx +151 -0
  587. package/src/features/issues/components/issues-priority-badge.tsx +77 -0
  588. package/src/features/issues/components/issues-status-badge.tsx +100 -0
  589. package/src/features/issues/components/metadata-section.tsx +389 -0
  590. package/src/features/issues/components/optimized-attachment-view.tsx +528 -0
  591. package/src/features/issues/components/optimized-image.tsx +257 -0
  592. package/src/features/issues/components/panel-header.tsx +186 -0
  593. package/src/features/issues/components/preload.ts +31 -0
  594. package/src/features/issues/components/priority-selector.tsx +101 -0
  595. package/src/features/issues/components/responsive-issue-layout-skeleton.tsx +139 -0
  596. package/src/features/issues/components/responsive-issue-layout.tsx +617 -0
  597. package/src/features/issues/components/status-selector.tsx +320 -0
  598. package/src/features/issues/components/type-selector.tsx +102 -0
  599. package/src/features/issues/components/upload-progress-overlay.tsx +35 -0
  600. package/src/features/issues/components/uploaded-image-preview.tsx +173 -0
  601. package/src/features/issues/components/workflow-control.tsx +318 -0
  602. package/src/features/issues/components/zoom-controls.tsx +150 -0
  603. package/src/features/issues/config/index.ts +47 -0
  604. package/src/features/issues/config/options.ts +323 -0
  605. package/src/features/issues/config/workflow.ts +102 -0
  606. package/src/features/issues/docs/ARCHITECTURE_DIAGRAM.md +321 -0
  607. package/src/features/issues/docs/BACKEND_ARCHITECTURE.md +194 -0
  608. package/src/features/issues/docs/IMAGE_COMPONENTS_ARCHITECTURE.md +363 -0
  609. package/src/features/issues/docs/IMAGE_COMPONENTS_IMPORTS.md +412 -0
  610. package/src/features/issues/docs/IMPLEMENTATION_CHECKLIST.md +210 -0
  611. package/src/features/issues/docs/ROUTE_SETUP_COMPLETE.md +242 -0
  612. package/src/features/issues/hooks/index.ts +78 -0
  613. package/src/features/issues/hooks/use-canvas-transform.ts +255 -0
  614. package/src/features/issues/hooks/use-create-issue.ts +28 -0
  615. package/src/features/issues/hooks/use-elastic-scroll.ts +296 -0
  616. package/src/features/issues/hooks/use-issue-activities.ts +71 -0
  617. package/src/features/issues/hooks/use-issue-delete.ts +84 -0
  618. package/src/features/issues/hooks/use-issue-details.ts +70 -0
  619. package/src/features/issues/hooks/use-issue-filters.ts +50 -0
  620. package/src/features/issues/hooks/use-issue-update.ts +93 -0
  621. package/src/features/issues/hooks/use-keyboard-shortcuts.ts +104 -0
  622. package/src/features/issues/hooks/use-optimized-image.ts +228 -0
  623. package/src/features/issues/hooks/use-project-issues.ts +14 -0
  624. package/src/features/issues/index.ts +65 -0
  625. package/src/features/issues/screens/issue-details-screen.tsx +207 -0
  626. package/src/features/issues/screens/issue-details-skeletons.tsx +295 -0
  627. package/src/features/issues/screens/issue-share-screen.tsx +56 -0
  628. package/src/features/issues/types/index.ts +48 -0
  629. package/src/features/issues/types/issue.ts +291 -0
  630. package/src/features/issues/utils/filter-issues.ts +141 -0
  631. package/src/features/issues/utils/index.ts +14 -0
  632. package/src/features/legal/index.ts +1 -0
  633. package/src/features/legal/screens/privacy-policy-screen.tsx +307 -0
  634. package/src/features/notifications/api/get-notifications.ts +58 -0
  635. package/src/features/notifications/api/get-unread-count.ts +37 -0
  636. package/src/features/notifications/api/index.ts +35 -0
  637. package/src/features/notifications/api/mark-all-as-read.ts +37 -0
  638. package/src/features/notifications/api/mark-as-read.ts +41 -0
  639. package/src/features/notifications/api/types.ts +109 -0
  640. package/src/features/notifications/hooks/__tests__/use-notification-subscription.test.ts +206 -0
  641. package/src/features/notifications/hooks/index.ts +28 -0
  642. package/src/features/notifications/hooks/use-mark-all-as-read.ts +106 -0
  643. package/src/features/notifications/hooks/use-mark-as-read.ts +106 -0
  644. package/src/features/notifications/hooks/use-notification-subscription.ts +244 -0
  645. package/src/features/notifications/hooks/use-notification-toast.ts +161 -0
  646. package/src/features/notifications/hooks/use-notifications.ts +80 -0
  647. package/src/features/notifications/hooks/use-unread-count.ts +60 -0
  648. package/src/features/notifications/index.ts +48 -0
  649. package/src/features/notifications/utils/group-notifications.ts +152 -0
  650. package/src/features/notifications/utils/index.ts +9 -0
  651. package/src/features/projects/api/create-invitation.ts +45 -0
  652. package/src/features/projects/api/create-project.ts +64 -0
  653. package/src/features/projects/api/delete-project.ts +50 -0
  654. package/src/features/projects/api/get-project-activities.ts +43 -0
  655. package/src/features/projects/api/get-project-members.ts +53 -0
  656. package/src/features/projects/api/get-project.ts +49 -0
  657. package/src/features/projects/api/get-projects.ts +61 -0
  658. package/src/features/projects/api/index.ts +27 -0
  659. package/src/features/projects/api/join-project.ts +52 -0
  660. package/src/features/projects/api/leave-project.ts +51 -0
  661. package/src/features/projects/api/list-invitations.ts +36 -0
  662. package/src/features/projects/api/remove-member.ts +60 -0
  663. package/src/features/projects/api/resend-invitation.ts +36 -0
  664. package/src/features/projects/api/revoke-invitation.ts +36 -0
  665. package/src/features/projects/api/types.ts +286 -0
  666. package/src/features/projects/api/update-member-role.ts +70 -0
  667. package/src/features/projects/api/update-project.ts +69 -0
  668. package/src/features/projects/components/__tests__/project-detail-activity-feed.test.tsx +106 -0
  669. package/src/features/projects/components/__tests__/project-invitation-dialog.test.tsx +211 -0
  670. package/src/features/projects/components/__tests__/project-member-manager-dialog.test.tsx +254 -0
  671. package/src/features/projects/components/index.ts +21 -0
  672. package/src/features/projects/components/project-actions.tsx +248 -0
  673. package/src/features/projects/components/project-create-dialog.tsx +410 -0
  674. package/src/features/projects/components/project-detail-activity-feed.tsx +206 -0
  675. package/src/features/projects/components/project-detail-header.tsx +103 -0
  676. package/src/features/projects/components/project-detail-overview.tsx +128 -0
  677. package/src/features/projects/components/project-icon-selector.test.tsx +49 -0
  678. package/src/features/projects/components/project-icon-selector.tsx +76 -0
  679. package/src/features/projects/components/project-invitation-dialog.tsx +368 -0
  680. package/src/features/projects/components/project-issues.tsx +128 -0
  681. package/src/features/projects/components/project-leave-button.tsx +69 -0
  682. package/src/features/projects/components/project-list-card.tsx +246 -0
  683. package/src/features/projects/components/project-list-filters.tsx +320 -0
  684. package/src/features/projects/components/project-member-manager-dialog.tsx +419 -0
  685. package/src/features/projects/components/project-settings-dialog.tsx +204 -0
  686. package/src/features/projects/components/project-stats.tsx +46 -0
  687. package/src/features/projects/components/project-title-section.tsx +78 -0
  688. package/src/features/projects/config/icons.ts +91 -0
  689. package/src/features/projects/hooks/index.ts +28 -0
  690. package/src/features/projects/hooks/use-create-invitation.ts +83 -0
  691. package/src/features/projects/hooks/use-create-project.ts +77 -0
  692. package/src/features/projects/hooks/use-delete-project.ts +84 -0
  693. package/src/features/projects/hooks/use-join-project.ts +43 -0
  694. package/src/features/projects/hooks/use-leave-project.ts +84 -0
  695. package/src/features/projects/hooks/use-project-activities.ts +39 -0
  696. package/src/features/projects/hooks/use-project-filters.ts +86 -0
  697. package/src/features/projects/hooks/use-project-invitations.ts +66 -0
  698. package/src/features/projects/hooks/use-project-members.ts +57 -0
  699. package/src/features/projects/hooks/use-project.ts +67 -0
  700. package/src/features/projects/hooks/use-projects.ts +49 -0
  701. package/src/features/projects/hooks/use-recent-projects.ts +58 -0
  702. package/src/features/projects/hooks/use-remove-member.ts +89 -0
  703. package/src/features/projects/hooks/use-resend-invitation.ts +68 -0
  704. package/src/features/projects/hooks/use-revoke-invitation.ts +71 -0
  705. package/src/features/projects/hooks/use-team-member-suggestions.ts +133 -0
  706. package/src/features/projects/hooks/use-update-member-role.ts +92 -0
  707. package/src/features/projects/hooks/use-update-project.ts +88 -0
  708. package/src/features/projects/index.ts +91 -0
  709. package/src/features/projects/screens/index.ts +3 -0
  710. package/src/features/projects/screens/invitation-acceptance-screen.tsx +320 -0
  711. package/src/features/projects/screens/project-detail-screen-wrapper.tsx +47 -0
  712. package/src/features/projects/screens/project-detail-screen.tsx +661 -0
  713. package/src/features/projects/screens/projects-list-screen.tsx +161 -0
  714. package/src/features/projects/types/index.ts +59 -0
  715. package/src/features/projects/utils/format-helpers.ts +16 -0
  716. package/src/features/projects/utils/index.ts +2 -0
  717. package/src/features/projects/utils/role-helpers.ts +25 -0
  718. package/src/features/setup/api/complete-setup.ts +21 -0
  719. package/src/features/setup/api/create-admin.ts +21 -0
  720. package/src/features/setup/api/create-first-workspace.ts +21 -0
  721. package/src/features/setup/api/get-instance-status.ts +12 -0
  722. package/src/features/setup/api/get-service-health.ts +12 -0
  723. package/src/features/setup/api/index.ts +44 -0
  724. package/src/features/setup/api/save-instance-config.ts +21 -0
  725. package/src/features/setup/api/types.ts +122 -0
  726. package/src/features/setup/components/__tests__/setup-wizard-ui.test.tsx +362 -0
  727. package/src/features/setup/components/admin-account-step.tsx +205 -0
  728. package/src/features/setup/components/first-workspace-step.tsx +120 -0
  729. package/src/features/setup/components/index.ts +9 -0
  730. package/src/features/setup/components/instance-config-step.tsx +107 -0
  731. package/src/features/setup/components/mail-config-step.tsx +205 -0
  732. package/src/features/setup/components/sample-data-step.tsx +131 -0
  733. package/src/features/setup/components/service-health-step.tsx +180 -0
  734. package/src/features/setup/components/service-status-badge.tsx +50 -0
  735. package/src/features/setup/components/setup-progress.tsx +103 -0
  736. package/src/features/setup/components/setup-wizard.tsx +169 -0
  737. package/src/features/setup/hooks/index.ts +12 -0
  738. package/src/features/setup/hooks/use-complete-setup.ts +23 -0
  739. package/src/features/setup/hooks/use-create-admin.ts +25 -0
  740. package/src/features/setup/hooks/use-create-first-workspace.ts +24 -0
  741. package/src/features/setup/hooks/use-instance-status.ts +21 -0
  742. package/src/features/setup/hooks/use-save-instance-config.ts +23 -0
  743. package/src/features/setup/hooks/use-service-health.ts +21 -0
  744. package/src/features/setup/hooks/use-setup-wizard.ts +152 -0
  745. package/src/features/setup/hooks/use-workspace-mode.ts +19 -0
  746. package/src/features/setup/index.ts +30 -0
  747. package/src/features/setup/screens/index.ts +1 -0
  748. package/src/features/setup/screens/setup-screen.tsx +40 -0
  749. package/src/features/setup/types/index.ts +69 -0
  750. package/src/features/setup/utils/index.ts +78 -0
  751. package/src/features/team-settings/components/index.ts +39 -0
  752. package/src/features/team-settings/components/loading-states.tsx +296 -0
  753. package/src/features/team-settings/components/permission-guard.tsx +23 -0
  754. package/src/features/team-settings/components/settings-card.tsx +22 -0
  755. package/src/features/team-settings/components/settings-context-provider.tsx +51 -0
  756. package/src/features/team-settings/components/settings-error-boundary.tsx +366 -0
  757. package/src/features/team-settings/components/settings-navigation.tsx +87 -0
  758. package/src/features/team-settings/components/settings-section.tsx +23 -0
  759. package/src/features/team-settings/components/team-danger-zone.tsx +275 -0
  760. package/src/features/team-settings/components/team-information-form.tsx +116 -0
  761. package/src/features/team-settings/components/team-invitations-list.tsx +463 -0
  762. package/src/features/team-settings/components/team-members-list.tsx +342 -0
  763. package/src/features/team-settings/components/team-permission-guard.tsx +56 -0
  764. package/src/features/team-settings/components/team-setting-integrations.tsx +27 -0
  765. package/src/features/team-settings/components/team-setting-member.tsx +28 -0
  766. package/src/features/team-settings/components/team-settings-general.tsx +131 -0
  767. package/src/features/team-settings/components/transfer-ownership-modal.tsx +164 -0
  768. package/src/features/team-settings/components/unauthorized-access.tsx +52 -0
  769. package/src/features/team-settings/hooks/__tests__/use-team-settings.test.tsx +139 -0
  770. package/src/features/team-settings/hooks/index.ts +1 -0
  771. package/src/features/team-settings/hooks/use-team-settings.ts +148 -0
  772. package/src/features/team-settings/hooks/use-transfer-ownership.ts +45 -0
  773. package/src/features/team-settings/index.ts +25 -0
  774. package/src/features/team-settings/screens/index.ts +1 -0
  775. package/src/features/team-settings/screens/team-settings-screen.tsx +33 -0
  776. package/src/features/team-settings/types/index.ts +78 -0
  777. package/src/features/team-settings/utils/index.ts +6 -0
  778. package/src/features/team-settings/utils/mock-data.ts +40 -0
  779. package/src/features/teams/api/cancel-invitation.ts +25 -0
  780. package/src/features/teams/api/create-invitation.ts +28 -0
  781. package/src/features/teams/api/create-team.ts +14 -0
  782. package/src/features/teams/api/delete-team.ts +19 -0
  783. package/src/features/teams/api/get-invitations.ts +25 -0
  784. package/src/features/teams/api/get-team-members.ts +25 -0
  785. package/src/features/teams/api/get-team.ts +13 -0
  786. package/src/features/teams/api/get-teams.ts +19 -0
  787. package/src/features/teams/api/index.ts +49 -0
  788. package/src/features/teams/api/leave-team.ts +22 -0
  789. package/src/features/teams/api/remove-member.ts +25 -0
  790. package/src/features/teams/api/resend-invitation.ts +26 -0
  791. package/src/features/teams/api/switch-team.ts +13 -0
  792. package/src/features/teams/api/types.ts +122 -0
  793. package/src/features/teams/api/update-member-roles.ts +28 -0
  794. package/src/features/teams/api/update-team.ts +17 -0
  795. package/src/features/teams/components/create-team-dialog.tsx +105 -0
  796. package/src/features/teams/hooks/__tests__/cache-invalidation.property.test.tsx +268 -0
  797. package/src/features/teams/hooks/index.ts +35 -0
  798. package/src/features/teams/hooks/use-can-manage-members.ts +21 -0
  799. package/src/features/teams/hooks/use-can-manage-team.ts +21 -0
  800. package/src/features/teams/hooks/use-cancel-invitation.ts +52 -0
  801. package/src/features/teams/hooks/use-create-invitation.ts +59 -0
  802. package/src/features/teams/hooks/use-create-team.ts +38 -0
  803. package/src/features/teams/hooks/use-delete-team.ts +43 -0
  804. package/src/features/teams/hooks/use-invitations.ts +31 -0
  805. package/src/features/teams/hooks/use-leave-team.ts +43 -0
  806. package/src/features/teams/hooks/use-remove-member.ts +58 -0
  807. package/src/features/teams/hooks/use-resend-invitation.ts +52 -0
  808. package/src/features/teams/hooks/use-switch-team.ts +41 -0
  809. package/src/features/teams/hooks/use-team-members.ts +31 -0
  810. package/src/features/teams/hooks/use-team-permissions.ts +102 -0
  811. package/src/features/teams/hooks/use-team.ts +30 -0
  812. package/src/features/teams/hooks/use-teams.ts +26 -0
  813. package/src/features/teams/hooks/use-update-member-roles.ts +64 -0
  814. package/src/features/teams/hooks/use-update-team.ts +47 -0
  815. package/src/features/teams/index.ts +111 -0
  816. package/src/features/user-settings/actions/set-password.ts +63 -0
  817. package/src/features/user-settings/api/index.ts +16 -0
  818. package/src/features/user-settings/components/__tests__/security-settings.test.tsx +125 -0
  819. package/src/features/user-settings/components/delete-account-dialog.tsx +185 -0
  820. package/src/features/user-settings/components/index.ts +13 -0
  821. package/src/features/user-settings/components/integrations-list.tsx +152 -0
  822. package/src/features/user-settings/components/notification-preferences.tsx +112 -0
  823. package/src/features/user-settings/components/other-settings.tsx +126 -0
  824. package/src/features/user-settings/components/password-section.tsx +297 -0
  825. package/src/features/user-settings/components/security-settings.tsx +184 -0
  826. package/src/features/user-settings/components/user-preferences.tsx +146 -0
  827. package/src/features/user-settings/hooks/index.ts +8 -0
  828. package/src/features/user-settings/hooks/use-notification-preferences.ts +65 -0
  829. package/src/features/user-settings/hooks/use-user-preferences.ts +52 -0
  830. package/src/features/user-settings/index.ts +22 -0
  831. package/src/features/user-settings/screens/index.ts +11 -0
  832. package/src/features/user-settings/screens/integrations-screen.tsx +24 -0
  833. package/src/features/user-settings/screens/notifications-screen.tsx +29 -0
  834. package/src/features/user-settings/screens/other-settings-screen.tsx +24 -0
  835. package/src/features/user-settings/screens/setting-preferences-screen.tsx +24 -0
  836. package/src/features/user-settings/screens/user-settings-screen.tsx +23 -0
  837. package/src/features/user-settings/types/index.ts +42 -0
  838. package/src/features/user-settings/utils/index.ts +8 -0
  839. package/src/hooks/use-long-press.ts +196 -0
  840. package/src/hooks/use-media-upload.ts +95 -0
  841. package/src/hooks/use-mobile.ts +19 -0
  842. package/src/hooks/use-team.ts +32 -0
  843. package/src/instrumentation.ts +14 -0
  844. package/src/lib/__tests__/auth-config.test.ts +166 -0
  845. package/src/lib/__tests__/config.test.ts +42 -0
  846. package/src/lib/__tests__/db.test.ts +101 -0
  847. package/src/lib/__tests__/env.property.test.ts +197 -0
  848. package/src/lib/__tests__/env.test.ts +177 -0
  849. package/src/lib/__tests__/health-check.test.ts +87 -0
  850. package/src/lib/__tests__/oauth-errors.test.ts +184 -0
  851. package/src/lib/__tests__/oauth-redirect.test.ts +224 -0
  852. package/src/lib/__tests__/oauth-scopes.test.ts +268 -0
  853. package/src/lib/__tests__/storage.test.ts +58 -0
  854. package/src/lib/__tests__/url-validator.test.ts +232 -0
  855. package/src/lib/api-client.ts +93 -0
  856. package/src/lib/auth-client.ts +5 -0
  857. package/src/lib/auth-config.ts +146 -0
  858. package/src/lib/auth.ts +209 -0
  859. package/src/lib/config.ts +228 -0
  860. package/src/lib/cors.ts +96 -0
  861. package/src/lib/date.ts +16 -0
  862. package/src/lib/db.ts +88 -0
  863. package/src/lib/env.ts +489 -0
  864. package/src/lib/feedback.ts +29 -0
  865. package/src/lib/health-check.ts +229 -0
  866. package/src/lib/logger.ts +204 -0
  867. package/src/lib/oauth-errors.ts +138 -0
  868. package/src/lib/performance.ts +367 -0
  869. package/src/lib/query-server.ts +35 -0
  870. package/src/lib/query.tsx +43 -0
  871. package/src/lib/resend.ts +36 -0
  872. package/src/lib/security-headers.ts +165 -0
  873. package/src/lib/setup-status.ts +34 -0
  874. package/src/lib/storage.ts +150 -0
  875. package/src/lib/supabase-client.ts +58 -0
  876. package/src/lib/testing/test-db.ts +75 -0
  877. package/src/lib/url-validator.ts +168 -0
  878. package/src/lib/utils.ts +6 -0
  879. package/src/mocks/README.md +42 -0
  880. package/src/mocks/activity.fixtures.ts +281 -0
  881. package/src/mocks/annotation.fixtures.ts +325 -0
  882. package/src/mocks/attachment.fixtures.ts +80 -0
  883. package/src/mocks/email.fixtures.ts +61 -0
  884. package/src/mocks/index.ts +33 -0
  885. package/src/mocks/issue.fixtures.ts +364 -0
  886. package/src/mocks/landing.fixtures.ts +118 -0
  887. package/src/mocks/notification.fixtures.ts +111 -0
  888. package/src/mocks/project-activity.fixtures.ts +69 -0
  889. package/src/mocks/project-invitation.fixtures.ts +95 -0
  890. package/src/mocks/project-member.fixtures.ts +93 -0
  891. package/src/mocks/project.fixtures.ts +249 -0
  892. package/src/mocks/settings.fixtures.ts +56 -0
  893. package/src/mocks/share.fixtures.ts +48 -0
  894. package/src/mocks/team-member.fixtures.ts +147 -0
  895. package/src/mocks/team.fixtures.ts +35 -0
  896. package/src/mocks/user-settings.fixtures.ts +77 -0
  897. package/src/mocks/user.fixtures.ts +21 -0
  898. package/src/providers/theme-provider.tsx +11 -0
  899. package/src/proxy/__tests__/proxy-setup-gate.test.ts +43 -0
  900. package/src/proxy.ts +169 -0
  901. package/src/server/annotations/__tests__/annotation-limit.property.test.ts +291 -0
  902. package/src/server/annotations/__tests__/attachment-deletion-cascade.property.test.ts +385 -0
  903. package/src/server/annotations/__tests__/comment-delete-authorization.property.test.ts +419 -0
  904. package/src/server/annotations/__tests__/sanitize.property.test.ts +302 -0
  905. package/src/server/annotations/annotation-service.ts +305 -0
  906. package/src/server/annotations/comment-service.ts +288 -0
  907. package/src/server/annotations/index.ts +61 -0
  908. package/src/server/annotations/permission-utils.ts +125 -0
  909. package/src/server/annotations/sanitize.ts +134 -0
  910. package/src/server/annotations/types.ts +161 -0
  911. package/src/server/audit/audit-service.ts +85 -0
  912. package/src/server/audit/index.ts +11 -0
  913. package/src/server/audit/types.ts +75 -0
  914. package/src/server/auth/__tests__/README.md +368 -0
  915. package/src/server/auth/__tests__/account-linking.integration.test.ts +410 -0
  916. package/src/server/auth/__tests__/auth-integration.test.ts +811 -0
  917. package/src/server/auth/__tests__/cookies.test.ts +337 -0
  918. package/src/server/auth/__tests__/login.property.test.ts +428 -0
  919. package/src/server/auth/__tests__/oauth-integration.test.ts +555 -0
  920. package/src/server/auth/__tests__/password.test.ts +194 -0
  921. package/src/server/auth/__tests__/rate-limiter.test.ts +450 -0
  922. package/src/server/auth/__tests__/rbac.test.ts +474 -0
  923. package/src/server/auth/__tests__/session.test.ts +599 -0
  924. package/src/server/auth/__tests__/signup.property.test.ts +224 -0
  925. package/src/server/auth/__tests__/token-encryption.test.ts +171 -0
  926. package/src/server/auth/__tests__/tokens.test.ts +476 -0
  927. package/src/server/auth/__tests__/verify-email.property.test.ts +372 -0
  928. package/src/server/auth/cookies.ts +184 -0
  929. package/src/server/auth/password.ts +94 -0
  930. package/src/server/auth/rate-limiter.ts +257 -0
  931. package/src/server/auth/rbac.ts +1168 -0
  932. package/src/server/auth/session.ts +392 -0
  933. package/src/server/auth/token-encryption.ts +201 -0
  934. package/src/server/auth/tokens.ts +397 -0
  935. package/src/server/db/schema/account.ts +21 -0
  936. package/src/server/db/schema/annotation-read-status.ts +57 -0
  937. package/src/server/db/schema/better-auth-verifications.ts +27 -0
  938. package/src/server/db/schema/email-jobs.ts +24 -0
  939. package/src/server/db/schema/index.ts +20 -0
  940. package/src/server/db/schema/instance-settings.ts +42 -0
  941. package/src/server/db/schema/issue-activities.ts +97 -0
  942. package/src/server/db/schema/issue-attachments.ts +101 -0
  943. package/src/server/db/schema/issues.ts +139 -0
  944. package/src/server/db/schema/notifications.ts +119 -0
  945. package/src/server/db/schema/project-activities.ts +116 -0
  946. package/src/server/db/schema/project-invitations.ts +34 -0
  947. package/src/server/db/schema/project-members.ts +29 -0
  948. package/src/server/db/schema/projects.ts +46 -0
  949. package/src/server/db/schema/sessions.ts +17 -0
  950. package/src/server/db/schema/team-invitations.ts +22 -0
  951. package/src/server/db/schema/team-members.ts +18 -0
  952. package/src/server/db/schema/teams.ts +15 -0
  953. package/src/server/db/schema/user-roles.ts +14 -0
  954. package/src/server/db/schema/users.ts +17 -0
  955. package/src/server/db/schema/verification-tokens.ts +15 -0
  956. package/src/server/email/README.md +258 -0
  957. package/src/server/email/__tests__/client.property.test.ts +218 -0
  958. package/src/server/email/__tests__/payload.property.test.ts +205 -0
  959. package/src/server/email/__tests__/queue.test.ts +407 -0
  960. package/src/server/email/__tests__/render-template.test.tsx +172 -0
  961. package/src/server/email/client.ts +70 -0
  962. package/src/server/email/index.ts +20 -0
  963. package/src/server/email/providers/console-provider.ts +43 -0
  964. package/src/server/email/providers/index.ts +5 -0
  965. package/src/server/email/providers/resend-provider.ts +59 -0
  966. package/src/server/email/providers/smtp-provider.ts +65 -0
  967. package/src/server/email/queue.ts +365 -0
  968. package/src/server/email/render-template.tsx +117 -0
  969. package/src/server/email/templates/layout.tsx +66 -0
  970. package/src/server/email/templates/ownership-transfer-email.tsx +75 -0
  971. package/src/server/email/templates/password-reset-email.tsx +72 -0
  972. package/src/server/email/templates/project-invitation-email.tsx +70 -0
  973. package/src/server/email/templates/security-alert-email.tsx +161 -0
  974. package/src/server/email/templates/team-invitation-email.tsx +68 -0
  975. package/src/server/email/templates/verification-email.tsx +61 -0
  976. package/src/server/email/templates/welcome-email.tsx +60 -0
  977. package/src/server/email/worker.ts +182 -0
  978. package/src/server/issues/activity-service.ts +422 -0
  979. package/src/server/issues/attachment-service.ts +379 -0
  980. package/src/server/issues/index.ts +68 -0
  981. package/src/server/issues/issue-service.ts +569 -0
  982. package/src/server/issues/types.ts +233 -0
  983. package/src/server/notifications/__tests__/notification-service.integration.test.ts +405 -0
  984. package/src/server/notifications/index.ts +13 -0
  985. package/src/server/notifications/notification-service.ts +526 -0
  986. package/src/server/notifications/types.ts +234 -0
  987. package/src/server/projects/__tests__/activity-logging.integration.test.ts +319 -0
  988. package/src/server/projects/__tests__/invitation-email.integration.test.ts +258 -0
  989. package/src/server/projects/__tests__/invitation-service.integration.test.ts +651 -0
  990. package/src/server/projects/__tests__/member-service.property.test.ts +397 -0
  991. package/src/server/projects/__tests__/project-service.property.test.ts +116 -0
  992. package/src/server/projects/activity-service.ts +548 -0
  993. package/src/server/projects/index.ts +11 -0
  994. package/src/server/projects/invitation-service.ts +773 -0
  995. package/src/server/projects/member-service.ts +458 -0
  996. package/src/server/projects/project-service.ts +491 -0
  997. package/src/server/projects/schemas.ts +310 -0
  998. package/src/server/projects/types.ts +166 -0
  999. package/src/server/projects/utils.ts +128 -0
  1000. package/src/server/setup/__tests__/health-check.property.test.ts +70 -0
  1001. package/src/server/setup/__tests__/sample-data.property.test.ts +82 -0
  1002. package/src/server/setup/__tests__/setup.property.test.ts +95 -0
  1003. package/src/server/setup/health-check-service.ts +294 -0
  1004. package/src/server/setup/index.ts +35 -0
  1005. package/src/server/setup/sample-data-service.ts +233 -0
  1006. package/src/server/setup/setup-service.ts +229 -0
  1007. package/src/server/setup/types.ts +96 -0
  1008. package/src/server/teams/__tests__/export.property.test.ts +542 -0
  1009. package/src/server/teams/__tests__/get-members.integration.test.ts +105 -0
  1010. package/src/server/teams/__tests__/invitation-flow.integration.test.ts +402 -0
  1011. package/src/server/teams/__tests__/invitations.property.test.ts +235 -0
  1012. package/src/server/teams/__tests__/member-management-flow.integration.test.ts +306 -0
  1013. package/src/server/teams/__tests__/members.property.test.ts +180 -0
  1014. package/src/server/teams/__tests__/ownership-transfer.property.test.ts +173 -0
  1015. package/src/server/teams/__tests__/resource-limits.property.test.ts +382 -0
  1016. package/src/server/teams/__tests__/team-context-management.integration.test.ts +396 -0
  1017. package/src/server/teams/__tests__/team-context.property.test.ts +854 -0
  1018. package/src/server/teams/__tests__/team-creation-flow.integration.test.ts +310 -0
  1019. package/src/server/teams/__tests__/team-creation.property.test.ts +280 -0
  1020. package/src/server/teams/errors.ts +396 -0
  1021. package/src/server/teams/export-service.ts +383 -0
  1022. package/src/server/teams/index.ts +10 -0
  1023. package/src/server/teams/invitation-service.ts +708 -0
  1024. package/src/server/teams/member-service.ts +334 -0
  1025. package/src/server/teams/resource-limits.ts +211 -0
  1026. package/src/server/teams/slug.ts +58 -0
  1027. package/src/server/teams/team-context.ts +648 -0
  1028. package/src/server/teams/team-service.ts +660 -0
  1029. package/src/server/teams/types.ts +81 -0
  1030. package/src/server/teams/validation.ts +209 -0
  1031. package/src/styles/globals.css +160 -0
  1032. package/src/types/deployment.ts +39 -0
  1033. package/supabase/config.toml +357 -0
  1034. package/supabase/seed.sql +480 -0
  1035. package/tests/e2e/.gitkeep +0 -0
  1036. package/tests/e2e/QUICK_START.md +98 -0
  1037. package/tests/e2e/README.md +301 -0
  1038. package/tests/e2e/auth.spec.ts +583 -0
  1039. package/tests/e2e/global-setup.ts +23 -0
  1040. package/tests/e2e/global-teardown.ts +23 -0
  1041. package/tests/e2e/helpers/auth-helpers.ts +310 -0
  1042. package/tests/e2e/helpers/test-fixtures.ts +286 -0
  1043. package/tests/e2e/smoke-test.spec.ts +330 -0
  1044. package/tsconfig.json +48 -0
  1045. package/vitest.config.ts +50 -0
  1046. package/vitest.setup.ts +56 -0
  1047. package/dist/index.js +0 -778
@@ -0,0 +1,1168 @@
1
+ // src/server/auth/rbac.ts
2
+
3
+ /**
4
+ * Role-Based Access Control (RBAC) Utilities
5
+ *
6
+ * This module provides server-side utilities for managing roles and permissions.
7
+ * All role assignments and permission checks should go through these functions.
8
+ */
9
+
10
+ import { and, eq } from "drizzle-orm";
11
+ import { db } from "@/lib/db";
12
+ import { userRoles } from "@/server/db/schema/user-roles";
13
+ import {
14
+ ALL_ROLES,
15
+ type Permission,
16
+ PROJECT_ROLES,
17
+ type ProjectRole,
18
+ type Role,
19
+ ROLE_PERMISSIONS,
20
+ TEAM_MANAGEMENT_ROLES,
21
+ type TeamManagementRole,
22
+ TEAM_OPERATIONAL_ROLES,
23
+ TEAM_OPERATIONAL_ROLE_HIERARCHY,
24
+ type TeamOperationalRole,
25
+ TEAM_ROLES,
26
+ type TeamRole,
27
+ } from "@/config/roles";
28
+ import { logger } from "@/lib/logger";
29
+
30
+ // ============================================================================
31
+ // TYPES
32
+ // ============================================================================
33
+
34
+ export interface UserRole {
35
+ id: string;
36
+ userId: string;
37
+ role: Role;
38
+ resourceType: "team" | "project";
39
+ resourceId: string;
40
+ createdAt: Date;
41
+ }
42
+
43
+ export interface RoleAssignment {
44
+ userId: string;
45
+ role: Role;
46
+ resourceType: "team" | "project";
47
+ resourceId: string;
48
+ }
49
+
50
+ export interface PermissionCheck {
51
+ userId: string;
52
+ permission: Permission;
53
+ resourceId: string;
54
+ resourceType?: "team" | "project";
55
+ }
56
+
57
+ // ============================================================================
58
+ // ROLE ASSIGNMENT
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Assign a role to a user for a specific resource.
63
+ *
64
+ * @param assignment - Role assignment details
65
+ * @returns The created user role record
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // Assign team owner role
70
+ * await assignRole({
71
+ * userId: 'user_123',
72
+ * role: TEAM_ROLES.TEAM_OWNER,
73
+ * resourceType: 'team',
74
+ * resourceId: 'team_456',
75
+ * });
76
+ *
77
+ * // Assign project editor role
78
+ * await assignRole({
79
+ * userId: 'user_123',
80
+ * role: PROJECT_ROLES.PROJECT_EDITOR,
81
+ * resourceType: 'project',
82
+ * resourceId: 'project_789',
83
+ * });
84
+ * ```
85
+ */
86
+ export async function assignRole(
87
+ assignment: RoleAssignment
88
+ ): Promise<UserRole> {
89
+ const { userId, role, resourceType, resourceId } = assignment;
90
+
91
+ // Validate role exists
92
+ if (!Object.values(ALL_ROLES).includes(role)) {
93
+ throw new Error(`Invalid role: ${role}`);
94
+ }
95
+
96
+ // Validate resource type matches role type
97
+ if (resourceType === "team" && !Object.values(TEAM_ROLES).includes(role as TeamRole)) {
98
+ throw new Error(`Role ${role} is not a team role`);
99
+ }
100
+ if (resourceType === "project" && !Object.values(PROJECT_ROLES).includes(role as ProjectRole)) {
101
+ throw new Error(`Role ${role} is not a project role`);
102
+ }
103
+
104
+ // Check if role already exists
105
+ const existing = await db
106
+ .select()
107
+ .from(userRoles)
108
+ .where(
109
+ and(
110
+ eq(userRoles.userId, userId),
111
+ eq(userRoles.role, role),
112
+ eq(userRoles.resourceType, resourceType),
113
+ eq(userRoles.resourceId, resourceId)
114
+ )
115
+ )
116
+ .limit(1);
117
+
118
+ if (existing.length > 0) {
119
+ logger.info("rbac.assign_role.already_exists", {
120
+ userId,
121
+ role,
122
+ resourceType,
123
+ resourceId,
124
+ });
125
+ return existing[0] as UserRole;
126
+ }
127
+
128
+ // Insert role
129
+ const [userRole] = await db
130
+ .insert(userRoles)
131
+ .values({
132
+ userId,
133
+ role,
134
+ resourceType,
135
+ resourceId,
136
+ })
137
+ .returning();
138
+
139
+ logger.info("rbac.assign_role.success", {
140
+ userId,
141
+ role,
142
+ resourceType,
143
+ resourceId,
144
+ roleId: userRole.id,
145
+ });
146
+
147
+ return userRole as UserRole;
148
+ }
149
+
150
+ /**
151
+ * Assign multiple roles to a user at once (transactional).
152
+ *
153
+ * @param assignments - Array of role assignments
154
+ * @returns Array of created user role records
155
+ */
156
+ export async function assignRoles(
157
+ assignments: RoleAssignment[]
158
+ ): Promise<UserRole[]> {
159
+ if (assignments.length === 0) {
160
+ return [];
161
+ }
162
+
163
+ // Validate all roles
164
+ for (const assignment of assignments) {
165
+ if (!Object.values(ALL_ROLES).includes(assignment.role)) {
166
+ throw new Error(`Invalid role: ${assignment.role}`);
167
+ }
168
+ }
169
+
170
+ // Insert all roles in a transaction
171
+ const roles = await db.transaction(async (tx) => {
172
+ const results: UserRole[] = [];
173
+
174
+ for (const assignment of assignments) {
175
+ const [userRole] = await tx
176
+ .insert(userRoles)
177
+ .values({
178
+ userId: assignment.userId,
179
+ role: assignment.role,
180
+ resourceType: assignment.resourceType,
181
+ resourceId: assignment.resourceId,
182
+ })
183
+ .returning();
184
+
185
+ results.push(userRole as UserRole);
186
+ }
187
+
188
+ return results;
189
+ });
190
+
191
+ logger.info("rbac.assign_roles.success", {
192
+ count: assignments.length,
193
+ userId: assignments[0].userId,
194
+ });
195
+
196
+ return roles;
197
+ }
198
+
199
+ /**
200
+ * Remove a role from a user.
201
+ *
202
+ * @param assignment - Role assignment to remove
203
+ * @returns True if role was removed, false if it didn't exist
204
+ */
205
+ export async function removeRole(assignment: RoleAssignment): Promise<boolean> {
206
+ const { userId, role, resourceType, resourceId } = assignment;
207
+
208
+ const result = await db
209
+ .delete(userRoles)
210
+ .where(
211
+ and(
212
+ eq(userRoles.userId, userId),
213
+ eq(userRoles.role, role),
214
+ eq(userRoles.resourceType, resourceType),
215
+ eq(userRoles.resourceId, resourceId)
216
+ )
217
+ )
218
+ .returning();
219
+
220
+ const removed = result.length > 0;
221
+
222
+ if (removed) {
223
+ logger.info("rbac.remove_role.success", {
224
+ userId,
225
+ role,
226
+ resourceType,
227
+ resourceId,
228
+ });
229
+ }
230
+
231
+ return removed;
232
+ }
233
+
234
+ /**
235
+ * Update a user's role for a resource (remove old role, assign new role).
236
+ *
237
+ * IMPORTANT: When demoting a TEAM_EDITOR to TEAM_VIEWER/TEAM_MEMBER,
238
+ * this function checks if the user is a PROJECT_OWNER on any projects.
239
+ * If they are, the demotion is blocked to prevent orphaned projects.
240
+ *
241
+ * @param userId - User ID
242
+ * @param oldRole - Current role to remove
243
+ * @param newRole - New role to assign
244
+ * @param resourceType - Resource type
245
+ * @param resourceId - Resource ID
246
+ * @throws Error if user is PROJECT_OWNER and being demoted from TEAM_EDITOR
247
+ */
248
+ export async function updateRole(
249
+ userId: string,
250
+ oldRole: Role,
251
+ newRole: Role,
252
+ resourceType: "team" | "project",
253
+ resourceId: string
254
+ ): Promise<void> {
255
+ // Check for PROJECT_OWNER demotion edge case
256
+ if (
257
+ resourceType === "team" &&
258
+ oldRole === "WORKSPACE_EDITOR" &&
259
+ (newRole === "WORKSPACE_VIEWER" || newRole === "WORKSPACE_MEMBER")
260
+ ) {
261
+ await validateProjectOwnershipBeforeDemotion(userId, resourceId);
262
+ }
263
+
264
+ await db.transaction(async (tx) => {
265
+ // Remove old role
266
+ await tx
267
+ .delete(userRoles)
268
+ .where(
269
+ and(
270
+ eq(userRoles.userId, userId),
271
+ eq(userRoles.role, oldRole),
272
+ eq(userRoles.resourceType, resourceType),
273
+ eq(userRoles.resourceId, resourceId)
274
+ )
275
+ );
276
+
277
+ // Assign new role
278
+ await tx.insert(userRoles).values({
279
+ userId,
280
+ role: newRole,
281
+ resourceType,
282
+ resourceId,
283
+ });
284
+ });
285
+
286
+ logger.info("rbac.update_role.success", {
287
+ userId,
288
+ oldRole,
289
+ newRole,
290
+ resourceType,
291
+ resourceId,
292
+ });
293
+ }
294
+
295
+ // ============================================================================
296
+ // ROLE QUERIES
297
+ // ============================================================================
298
+
299
+ /**
300
+ * Get all roles for a user.
301
+ *
302
+ * @param userId - User ID
303
+ * @returns Array of user roles
304
+ */
305
+ export async function getUserRoles(userId: string): Promise<UserRole[]> {
306
+ const roles = await db
307
+ .select()
308
+ .from(userRoles)
309
+ .where(eq(userRoles.userId, userId));
310
+
311
+ return roles as UserRole[];
312
+ }
313
+
314
+ /**
315
+ * Get all roles for a user in a specific team.
316
+ *
317
+ * @param userId - User ID
318
+ * @param teamId - Team ID
319
+ * @returns Array of team roles
320
+ */
321
+ export async function getUserTeamRoles(
322
+ userId: string,
323
+ teamId: string
324
+ ): Promise<UserRole[]> {
325
+ const roles = await db
326
+ .select()
327
+ .from(userRoles)
328
+ .where(
329
+ and(
330
+ eq(userRoles.userId, userId),
331
+ eq(userRoles.resourceType, "team"),
332
+ eq(userRoles.resourceId, teamId)
333
+ )
334
+ );
335
+
336
+ return roles as UserRole[];
337
+ }
338
+
339
+ /**
340
+ * Get all roles for a user in a specific project.
341
+ *
342
+ * @param userId - User ID
343
+ * @param projectId - Project ID
344
+ * @returns Array of project roles
345
+ */
346
+ export async function getUserProjectRoles(
347
+ userId: string,
348
+ projectId: string
349
+ ): Promise<UserRole[]> {
350
+ const roles = await db
351
+ .select()
352
+ .from(userRoles)
353
+ .where(
354
+ and(
355
+ eq(userRoles.userId, userId),
356
+ eq(userRoles.resourceType, "project"),
357
+ eq(userRoles.resourceId, projectId)
358
+ )
359
+ );
360
+
361
+ return roles as UserRole[];
362
+ }
363
+
364
+ /**
365
+ * Get the highest team role for a user in a team.
366
+ *
367
+ * @param userId - User ID
368
+ * @param teamId - Team ID
369
+ * @returns Highest team role or null if user has no team roles
370
+ */
371
+ export async function getHighestTeamRole(
372
+ userId: string,
373
+ teamId: string
374
+ ): Promise<TeamRole | null> {
375
+ const roles = await getUserTeamRoles(userId, teamId);
376
+
377
+ if (roles.length === 0) {
378
+ return null;
379
+ }
380
+
381
+ // Find highest role by hierarchy
382
+ const teamRoleHierarchy = {
383
+ [TEAM_ROLES.TEAM_VIEWER]: 1,
384
+ [TEAM_ROLES.TEAM_MEMBER]: 2,
385
+ [TEAM_ROLES.TEAM_EDITOR]: 3,
386
+ [TEAM_ROLES.TEAM_ADMIN]: 4,
387
+ [TEAM_ROLES.TEAM_OWNER]: 5,
388
+ };
389
+
390
+ let highestRole = roles[0].role as TeamRole;
391
+ let highestLevel = teamRoleHierarchy[highestRole];
392
+
393
+ for (const role of roles) {
394
+ const level = teamRoleHierarchy[role.role as TeamRole];
395
+ if (level > highestLevel) {
396
+ highestRole = role.role as TeamRole;
397
+ highestLevel = level;
398
+ }
399
+ }
400
+
401
+ return highestRole;
402
+ }
403
+
404
+ /**
405
+ * Get the highest project role for a user in a project.
406
+ *
407
+ * @param userId - User ID
408
+ * @param projectId - Project ID
409
+ * @returns Highest project role or null if user has no project roles
410
+ */
411
+ export async function getHighestProjectRole(
412
+ userId: string,
413
+ projectId: string
414
+ ): Promise<ProjectRole | null> {
415
+ const roles = await getUserProjectRoles(userId, projectId);
416
+
417
+ if (roles.length === 0) {
418
+ return null;
419
+ }
420
+
421
+ // Find highest role by hierarchy
422
+ const projectRoleHierarchy = {
423
+ [PROJECT_ROLES.PROJECT_VIEWER]: 1,
424
+ [PROJECT_ROLES.PROJECT_DEVELOPER]: 2,
425
+ [PROJECT_ROLES.PROJECT_EDITOR]: 3,
426
+ [PROJECT_ROLES.PROJECT_OWNER]: 4,
427
+ };
428
+
429
+ let highestRole = roles[0].role as ProjectRole;
430
+ let highestLevel = projectRoleHierarchy[highestRole];
431
+
432
+ for (const role of roles) {
433
+ const level = projectRoleHierarchy[role.role as ProjectRole];
434
+ if (level > highestLevel) {
435
+ highestRole = role.role as ProjectRole;
436
+ highestLevel = level;
437
+ }
438
+ }
439
+
440
+ return highestRole;
441
+ }
442
+
443
+ /**
444
+ * Check if a user has a specific role.
445
+ *
446
+ * For team roles, this queries the team_members table (single source of truth).
447
+ * For project roles, this queries the project_members table.
448
+ *
449
+ * @param userId - User ID
450
+ * @param role - Role to check
451
+ * @param resourceType - Resource type
452
+ * @param resourceId - Resource ID
453
+ * @returns True if user has the role
454
+ */
455
+ export async function hasRole(
456
+ userId: string,
457
+ role: Role,
458
+ resourceType: "team" | "project",
459
+ resourceId: string
460
+ ): Promise<boolean> {
461
+ if (resourceType === "team") {
462
+ // Query team_members table (single source of truth for team roles)
463
+ const { teamMembers } = await import("@/server/db/schema/team-members");
464
+
465
+ const teamMember = await db
466
+ .select()
467
+ .from(teamMembers)
468
+ .where(
469
+ and(
470
+ eq(teamMembers.teamId, resourceId),
471
+ eq(teamMembers.userId, userId)
472
+ )
473
+ )
474
+ .limit(1);
475
+
476
+ if (teamMember.length === 0) {
477
+ return false;
478
+ }
479
+
480
+ const member = teamMember[0];
481
+ // Check if the requested role matches either management or operational role
482
+ return member.managementRole === role || member.operationalRole === role;
483
+ }
484
+
485
+ if (resourceType === "project") {
486
+ // Query project_members table (single source of truth for project roles)
487
+ const { projectMembers } = await import("@/server/db/schema/project-members");
488
+
489
+ const projectMember = await db
490
+ .select()
491
+ .from(projectMembers)
492
+ .where(
493
+ and(
494
+ eq(projectMembers.projectId, resourceId),
495
+ eq(projectMembers.userId, userId)
496
+ )
497
+ )
498
+ .limit(1);
499
+
500
+ if (projectMember.length === 0) {
501
+ return false;
502
+ }
503
+
504
+ return projectMember[0].role === role;
505
+ }
506
+
507
+ // Fallback: query user_roles for unknown resource types (backwards compatibility)
508
+ const result = await db
509
+ .select()
510
+ .from(userRoles)
511
+ .where(
512
+ and(
513
+ eq(userRoles.userId, userId),
514
+ eq(userRoles.role, role),
515
+ eq(userRoles.resourceType, resourceType),
516
+ eq(userRoles.resourceId, resourceId)
517
+ )
518
+ )
519
+ .limit(1);
520
+
521
+ return result.length > 0;
522
+ }
523
+
524
+ // ============================================================================
525
+ // PERMISSION CHECKS
526
+ // ============================================================================
527
+
528
+ /**
529
+ * Check if a user has a specific permission for a resource.
530
+ *
531
+ * @param check - Permission check details
532
+ * @returns True if user has the permission
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * // Check if user can create issues in a project
537
+ * const canCreate = await hasPermission({
538
+ * userId: 'user_123',
539
+ * permission: PERMISSIONS.ISSUE_CREATE,
540
+ * resourceId: 'project_456',
541
+ * resourceType: 'project',
542
+ * });
543
+ * ```
544
+ */
545
+ export async function hasPermission(check: PermissionCheck): Promise<boolean> {
546
+ const { userId, permission, resourceId, resourceType } = check;
547
+
548
+ // ============================================================================
549
+ // CONSOLIDATED PERMISSION CHECK
550
+ // Query the single source of truth table based on resource type:
551
+ // - team resources → team_members table
552
+ // - project resources → project_members table
553
+ // ============================================================================
554
+
555
+ if (resourceType === "project") {
556
+ // Query project_members for project permissions
557
+ const { projectMembers } = await import("@/server/db/schema/project-members");
558
+
559
+ const projectMember = await db
560
+ .select()
561
+ .from(projectMembers)
562
+ .where(
563
+ and(
564
+ eq(projectMembers.projectId, resourceId),
565
+ eq(projectMembers.userId, userId)
566
+ )
567
+ )
568
+ .limit(1);
569
+
570
+ if (projectMember.length > 0) {
571
+ const member = projectMember[0];
572
+ const rolePermissions = ROLE_PERMISSIONS[member.role as Role];
573
+ if (rolePermissions?.includes(permission)) {
574
+ return true;
575
+ }
576
+ }
577
+ return false;
578
+ }
579
+
580
+ if (resourceType === "team") {
581
+ // Query team_members for team permissions (single source of truth)
582
+ const { teamMembers } = await import("@/server/db/schema/team-members");
583
+
584
+ const teamMember = await db
585
+ .select()
586
+ .from(teamMembers)
587
+ .where(
588
+ and(
589
+ eq(teamMembers.teamId, resourceId),
590
+ eq(teamMembers.userId, userId)
591
+ )
592
+ )
593
+ .limit(1);
594
+
595
+ if (teamMember.length > 0) {
596
+ const member = teamMember[0];
597
+
598
+ // Check management role permissions (TEAM_OWNER, TEAM_ADMIN)
599
+ if (member.managementRole) {
600
+ const managementPermissions = ROLE_PERMISSIONS[member.managementRole as Role];
601
+ if (managementPermissions?.includes(permission)) {
602
+ return true;
603
+ }
604
+ }
605
+
606
+ // Check operational role permissions (TEAM_EDITOR, TEAM_MEMBER, TEAM_VIEWER)
607
+ if (member.operationalRole) {
608
+ const operationalPermissions = ROLE_PERMISSIONS[member.operationalRole as Role];
609
+ if (operationalPermissions?.includes(permission)) {
610
+ return true;
611
+ }
612
+ }
613
+ }
614
+ return false;
615
+ }
616
+
617
+ // Fallback: query user_roles for unknown resource types (backwards compatibility)
618
+ const roles = await db
619
+ .select()
620
+ .from(userRoles)
621
+ .where(
622
+ and(
623
+ eq(userRoles.userId, userId),
624
+ eq(userRoles.resourceId, resourceId)
625
+ )
626
+ );
627
+
628
+ for (const userRole of roles) {
629
+ const rolePermissions = ROLE_PERMISSIONS[userRole.role as Role];
630
+ if (rolePermissions?.includes(permission)) {
631
+ return true;
632
+ }
633
+ }
634
+
635
+ return false;
636
+ }
637
+
638
+
639
+
640
+ /**
641
+ * Check if a user has any of the specified permissions for a resource.
642
+ *
643
+ * @param userId - User ID
644
+ * @param permissions - Array of permissions to check
645
+ * @param resourceId - Resource ID
646
+ * @param resourceType - Resource type (optional)
647
+ * @returns True if user has at least one of the permissions
648
+ */
649
+ export async function hasAnyPermission(
650
+ userId: string,
651
+ permissions: Permission[],
652
+ resourceId: string,
653
+ resourceType?: "team" | "project"
654
+ ): Promise<boolean> {
655
+ for (const permission of permissions) {
656
+ const has = await hasPermission({
657
+ userId,
658
+ permission,
659
+ resourceId,
660
+ resourceType,
661
+ });
662
+ if (has) {
663
+ return true;
664
+ }
665
+ }
666
+ return false;
667
+ }
668
+
669
+ /**
670
+ * Check if a user has all of the specified permissions for a resource.
671
+ *
672
+ * @param userId - User ID
673
+ * @param permissions - Array of permissions to check
674
+ * @param resourceId - Resource ID
675
+ * @param resourceType - Resource type (optional)
676
+ * @returns True if user has all of the permissions
677
+ */
678
+ export async function hasAllPermissions(
679
+ userId: string,
680
+ permissions: Permission[],
681
+ resourceId: string,
682
+ resourceType?: "team" | "project"
683
+ ): Promise<boolean> {
684
+ for (const permission of permissions) {
685
+ const has = await hasPermission({
686
+ userId,
687
+ permission,
688
+ resourceId,
689
+ resourceType,
690
+ });
691
+ if (!has) {
692
+ return false;
693
+ }
694
+ }
695
+ return true;
696
+ }
697
+
698
+ /**
699
+ * Get all permissions for a user in a resource.
700
+ *
701
+ * @param userId - User ID
702
+ * @param resourceId - Resource ID
703
+ * @param resourceType - Resource type (optional)
704
+ * @returns Array of permissions
705
+ */
706
+ export async function getUserPermissions(
707
+ userId: string,
708
+ resourceId: string,
709
+ resourceType?: "team" | "project"
710
+ ): Promise<Permission[]> {
711
+ // Collect all permissions from all roles (deduplicated)
712
+ const permissionsSet = new Set<Permission>();
713
+
714
+ if (resourceType === "project") {
715
+ // Query project_members for project permissions
716
+ const { projectMembers } = await import("@/server/db/schema/project-members");
717
+
718
+ const projectMember = await db
719
+ .select()
720
+ .from(projectMembers)
721
+ .where(
722
+ and(
723
+ eq(projectMembers.projectId, resourceId),
724
+ eq(projectMembers.userId, userId)
725
+ )
726
+ )
727
+ .limit(1);
728
+
729
+ if (projectMember.length > 0) {
730
+ const member = projectMember[0];
731
+ const rolePermissions = ROLE_PERMISSIONS[member.role as Role];
732
+ if (rolePermissions) {
733
+ for (const permission of rolePermissions) {
734
+ permissionsSet.add(permission);
735
+ }
736
+ }
737
+ }
738
+ return Array.from(permissionsSet);
739
+ }
740
+
741
+ if (resourceType === "team") {
742
+ // Query team_members for team permissions (single source of truth)
743
+ const { teamMembers } = await import("@/server/db/schema/team-members");
744
+
745
+ const teamMember = await db
746
+ .select()
747
+ .from(teamMembers)
748
+ .where(
749
+ and(
750
+ eq(teamMembers.teamId, resourceId),
751
+ eq(teamMembers.userId, userId)
752
+ )
753
+ )
754
+ .limit(1);
755
+
756
+ if (teamMember.length > 0) {
757
+ const member = teamMember[0];
758
+
759
+ // Add management role permissions
760
+ if (member.managementRole) {
761
+ const managementPermissions = ROLE_PERMISSIONS[member.managementRole as Role];
762
+ if (managementPermissions) {
763
+ for (const permission of managementPermissions) {
764
+ permissionsSet.add(permission);
765
+ }
766
+ }
767
+ }
768
+
769
+ // Add operational role permissions
770
+ if (member.operationalRole) {
771
+ const operationalPermissions = ROLE_PERMISSIONS[member.operationalRole as Role];
772
+ if (operationalPermissions) {
773
+ for (const permission of operationalPermissions) {
774
+ permissionsSet.add(permission);
775
+ }
776
+ }
777
+ }
778
+ }
779
+ return Array.from(permissionsSet);
780
+ }
781
+
782
+ // Fallback: query user_roles for unknown resource types
783
+ const roles = await db
784
+ .select()
785
+ .from(userRoles)
786
+ .where(
787
+ and(
788
+ eq(userRoles.userId, userId),
789
+ eq(userRoles.resourceId, resourceId)
790
+ )
791
+ );
792
+
793
+ for (const userRole of roles) {
794
+ const rolePermissions = ROLE_PERMISSIONS[userRole.role as Role];
795
+ if (rolePermissions) {
796
+ for (const permission of rolePermissions) {
797
+ permissionsSet.add(permission);
798
+ }
799
+ }
800
+ }
801
+
802
+ return Array.from(permissionsSet);
803
+ }
804
+
805
+
806
+ // ============================================================================
807
+ // TWO-TIER ROLE HELPERS
808
+ // ============================================================================
809
+
810
+ /**
811
+ * Get a user's management role for a team.
812
+ *
813
+ * @param userId - User ID
814
+ * @param teamId - Team ID
815
+ * @returns Management role or null if user has no management role
816
+ */
817
+ export async function getManagementRole(
818
+ userId: string,
819
+ teamId: string
820
+ ): Promise<TeamManagementRole | null> {
821
+ const roles = await db
822
+ .select()
823
+ .from(userRoles)
824
+ .where(
825
+ and(
826
+ eq(userRoles.userId, userId),
827
+ eq(userRoles.resourceType, "team"),
828
+ eq(userRoles.resourceId, teamId)
829
+ )
830
+ );
831
+
832
+ for (const role of roles) {
833
+ if (role.role === "WORKSPACE_OWNER" || role.role === "WORKSPACE_ADMIN") {
834
+ return role.role as TeamManagementRole;
835
+ }
836
+ }
837
+
838
+ return null;
839
+ }
840
+
841
+ /**
842
+ * Get a user's operational role for a team.
843
+ *
844
+ * @param userId - User ID
845
+ * @param teamId - Team ID
846
+ * @returns Operational role or null if user has no operational role
847
+ */
848
+ export async function getOperationalRole(
849
+ userId: string,
850
+ teamId: string
851
+ ): Promise<TeamOperationalRole | null> {
852
+ const roles = await db
853
+ .select()
854
+ .from(userRoles)
855
+ .where(
856
+ and(
857
+ eq(userRoles.userId, userId),
858
+ eq(userRoles.resourceType, "team"),
859
+ eq(userRoles.resourceId, teamId)
860
+ )
861
+ );
862
+
863
+ for (const role of roles) {
864
+ if (
865
+ role.role === "WORKSPACE_EDITOR" ||
866
+ role.role === "WORKSPACE_MEMBER" ||
867
+ role.role === "WORKSPACE_VIEWER"
868
+ ) {
869
+ return role.role as TeamOperationalRole;
870
+ }
871
+ }
872
+
873
+ return null;
874
+ }
875
+
876
+ /**
877
+ * Ensure a user has a specific operational role.
878
+ * If user doesn't have the role, assign it.
879
+ * If user has a lower role, upgrade it.
880
+ *
881
+ * IMPORTANT: This function only upgrades, never downgrades.
882
+ * Use updateRole() directly for downgrades (which includes PROJECT_OWNER validation).
883
+ *
884
+ * @param userId - User ID
885
+ * @param teamId - Team ID
886
+ * @param role - Desired operational role
887
+ */
888
+ export async function ensureOperationalRole(
889
+ userId: string,
890
+ teamId: string,
891
+ role: TeamOperationalRole
892
+ ): Promise<void> {
893
+ const currentRole = await getOperationalRole(userId, teamId);
894
+
895
+ if (!currentRole) {
896
+ // User has no operational role, assign it
897
+ await assignRole({
898
+ userId,
899
+ role,
900
+ resourceType: "team",
901
+ resourceId: teamId,
902
+ });
903
+ return;
904
+ }
905
+
906
+ if (currentRole === role) {
907
+ // User already has the desired role
908
+ return;
909
+ }
910
+
911
+ // Check if we need to upgrade
912
+ const currentLevel = TEAM_OPERATIONAL_ROLE_HIERARCHY[currentRole];
913
+ const desiredLevel = TEAM_OPERATIONAL_ROLE_HIERARCHY[role];
914
+
915
+ if (desiredLevel > currentLevel) {
916
+ // Upgrade to higher role (safe, no PROJECT_OWNER check needed)
917
+ await updateRole(userId, currentRole, role, "team", teamId);
918
+ }
919
+
920
+ // Don't downgrade automatically - must be done explicitly via updateRole()
921
+ }
922
+
923
+ /**
924
+ * Validate that a user can be demoted from TEAM_EDITOR.
925
+ * Checks if user is PROJECT_OWNER on any projects in the team.
926
+ *
927
+ * @param userId - User ID
928
+ * @param teamId - Team ID
929
+ * @throws Error with project details if user is PROJECT_OWNER
930
+ */
931
+ async function validateProjectOwnershipBeforeDemotion(
932
+ userId: string,
933
+ teamId: string
934
+ ): Promise<void> {
935
+ // Get all projects where user is PROJECT_OWNER
936
+ const ownedProjects = await db
937
+ .select()
938
+ .from(userRoles)
939
+ .where(
940
+ and(
941
+ eq(userRoles.userId, userId),
942
+ eq(userRoles.role, "PROJECT_OWNER"),
943
+ eq(userRoles.resourceType, "project")
944
+ )
945
+ );
946
+
947
+ if (ownedProjects.length === 0) {
948
+ // User is not a PROJECT_OWNER, demotion is safe
949
+ return;
950
+ }
951
+
952
+ // TODO: Fetch project names from projects table when it's implemented
953
+ // For now, just use project IDs
954
+ const projectIds = ownedProjects.map((p) => p.resourceId);
955
+
956
+ logger.warn("rbac.demotion_blocked.project_owner", {
957
+ userId,
958
+ teamId,
959
+ projectCount: projectIds.length,
960
+ projectIds,
961
+ });
962
+
963
+ throw new Error(
964
+ `DEMOTION_BLOCKED: User is PROJECT_OWNER on ${projectIds.length} project(s). ` +
965
+ `Transfer ownership first. Project IDs: ${projectIds.join(", ")}`
966
+ );
967
+ }
968
+
969
+ /**
970
+ * Get all projects where a user is PROJECT_OWNER.
971
+ * Useful for UI to show which projects need ownership transfer.
972
+ *
973
+ * @param userId - User ID
974
+ * @returns Array of project IDs where user is owner
975
+ */
976
+ export async function getOwnedProjects(userId: string): Promise<string[]> {
977
+ const ownedProjects = await db
978
+ .select()
979
+ .from(userRoles)
980
+ .where(
981
+ and(
982
+ eq(userRoles.userId, userId),
983
+ eq(userRoles.role, "PROJECT_OWNER"),
984
+ eq(userRoles.resourceType, "project")
985
+ )
986
+ );
987
+
988
+ return ownedProjects.map((p) => p.resourceId);
989
+ }
990
+
991
+ /**
992
+ * Auto-promote user to TEAM_EDITOR when they get PROJECT_OWNER or PROJECT_EDITOR role.
993
+ *
994
+ * @param userId - User ID
995
+ * @param teamId - Team ID
996
+ */
997
+ export async function autoPromoteToEditor(
998
+ userId: string,
999
+ teamId: string
1000
+ ): Promise<void> {
1001
+ await ensureOperationalRole(userId, teamId, "WORKSPACE_EDITOR");
1002
+ }
1003
+
1004
+ /**
1005
+ * Safely demote a user from TEAM_EDITOR to TEAM_VIEWER/TEAM_MEMBER.
1006
+ * Transfers PROJECT_OWNER roles to a new owner before demotion.
1007
+ *
1008
+ * @param userId - User ID to demote
1009
+ * @param teamId - Team ID
1010
+ * @param newOperationalRole - New operational role (TEAM_VIEWER or TEAM_MEMBER)
1011
+ * @param projectOwnershipTransfers - Map of projectId -> newOwnerId for ownership transfers
1012
+ *
1013
+ * @example
1014
+ * ```ts
1015
+ * // Demote user and transfer their project ownerships
1016
+ * await demoteWithOwnershipTransfer(
1017
+ * 'user_123',
1018
+ * 'team_456',
1019
+ * 'WORKSPACE_VIEWER',
1020
+ * {
1021
+ * 'project_1': 'user_789', // Transfer project_1 to user_789
1022
+ * 'project_2': 'user_789', // Transfer project_2 to user_789
1023
+ * }
1024
+ * );
1025
+ * ```
1026
+ */
1027
+ export async function demoteWithOwnershipTransfer(
1028
+ userId: string,
1029
+ teamId: string,
1030
+ newOperationalRole: "WORKSPACE_VIEWER" | "WORKSPACE_MEMBER",
1031
+ projectOwnershipTransfers: Record<string, string>
1032
+ ): Promise<void> {
1033
+ // Get all projects where user is PROJECT_OWNER
1034
+ const ownedProjectIds = await getOwnedProjects(userId);
1035
+
1036
+ // Validate that all owned projects have a transfer target
1037
+ for (const projectId of ownedProjectIds) {
1038
+ if (!projectOwnershipTransfers[projectId]) {
1039
+ throw new Error(
1040
+ `Missing ownership transfer for project ${projectId}. ` +
1041
+ `All owned projects must have a new owner assigned.`
1042
+ );
1043
+ }
1044
+ }
1045
+
1046
+ // Perform transfers and demotion in a transaction
1047
+ await db.transaction(async (tx) => {
1048
+ // Transfer ownership for each project
1049
+ for (const [projectId, newOwnerId] of Object.entries(projectOwnershipTransfers)) {
1050
+ // Remove old owner's PROJECT_OWNER role
1051
+ await tx
1052
+ .delete(userRoles)
1053
+ .where(
1054
+ and(
1055
+ eq(userRoles.userId, userId),
1056
+ eq(userRoles.role, "PROJECT_OWNER"),
1057
+ eq(userRoles.resourceType, "project"),
1058
+ eq(userRoles.resourceId, projectId)
1059
+ )
1060
+ );
1061
+
1062
+ // Assign PROJECT_OWNER to new owner
1063
+ await tx.insert(userRoles).values({
1064
+ userId: newOwnerId,
1065
+ role: "PROJECT_OWNER",
1066
+ resourceType: "project",
1067
+ resourceId: projectId,
1068
+ });
1069
+
1070
+ // Auto-promote new owner to TEAM_EDITOR
1071
+ await autoPromoteToEditor(newOwnerId, teamId);
1072
+
1073
+ logger.info("rbac.project_ownership_transferred", {
1074
+ projectId,
1075
+ fromUserId: userId,
1076
+ toUserId: newOwnerId,
1077
+ });
1078
+ }
1079
+
1080
+ // Now safe to demote the user
1081
+ const currentRole = await getOperationalRole(userId, teamId);
1082
+ if (currentRole) {
1083
+ await tx
1084
+ .delete(userRoles)
1085
+ .where(
1086
+ and(
1087
+ eq(userRoles.userId, userId),
1088
+ eq(userRoles.role, currentRole),
1089
+ eq(userRoles.resourceType, "team"),
1090
+ eq(userRoles.resourceId, teamId)
1091
+ )
1092
+ );
1093
+ }
1094
+
1095
+ // Assign new operational role
1096
+ await tx.insert(userRoles).values({
1097
+ userId,
1098
+ role: newOperationalRole,
1099
+ resourceType: "team",
1100
+ resourceId: teamId,
1101
+ });
1102
+ });
1103
+
1104
+ logger.info("rbac.demote_with_transfer.success", {
1105
+ userId,
1106
+ teamId,
1107
+ newRole: newOperationalRole,
1108
+ projectsTransferred: Object.keys(projectOwnershipTransfers).length,
1109
+ });
1110
+ }
1111
+
1112
+ // ============================================================================
1113
+ // AUTHORIZATION GUARDS
1114
+ // ============================================================================
1115
+
1116
+ /**
1117
+ * Require a user to have a specific permission, or throw an error.
1118
+ *
1119
+ * @param check - Permission check details
1120
+ * @throws Error if user doesn't have the permission
1121
+ */
1122
+ export async function requirePermission(check: PermissionCheck): Promise<void> {
1123
+ const has = await hasPermission(check);
1124
+
1125
+ if (!has) {
1126
+ logger.warn("rbac.permission_denied", {
1127
+ userId: check.userId,
1128
+ permission: check.permission,
1129
+ resourceId: check.resourceId,
1130
+ resourceType: check.resourceType,
1131
+ });
1132
+
1133
+ throw new Error(
1134
+ `Permission denied: ${check.permission} on ${check.resourceType || "resource"} ${check.resourceId}`
1135
+ );
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Require a user to have a specific role, or throw an error.
1141
+ *
1142
+ * @param userId - User ID
1143
+ * @param role - Required role
1144
+ * @param resourceType - Resource type
1145
+ * @param resourceId - Resource ID
1146
+ * @throws Error if user doesn't have the role
1147
+ */
1148
+ export async function requireRole(
1149
+ userId: string,
1150
+ role: Role,
1151
+ resourceType: "team" | "project",
1152
+ resourceId: string
1153
+ ): Promise<void> {
1154
+ const has = await hasRole(userId, role, resourceType, resourceId);
1155
+
1156
+ if (!has) {
1157
+ logger.warn("rbac.role_denied", {
1158
+ userId,
1159
+ role,
1160
+ resourceType,
1161
+ resourceId,
1162
+ });
1163
+
1164
+ throw new Error(
1165
+ `Role required: ${role} on ${resourceType} ${resourceId}`
1166
+ );
1167
+ }
1168
+ }