@podosoft/podokit 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +29 -11
  4. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +3 -0
  5. package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
  6. package/dist/templates/fullstack-nest-svelte/apps/web/package.json +8 -6
  7. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
  8. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
  9. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
  10. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
  11. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
  12. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/alert/index.ts +17 -0
  13. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
  14. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
  15. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
  16. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
  17. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
  18. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
  19. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
  20. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
  21. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/badge/index.ts +2 -0
  22. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  23. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
  24. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
  28. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  29. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  30. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  31. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  32. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
  33. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  34. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
  35. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  36. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  37. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  38. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  39. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  40. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  41. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  42. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  43. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  44. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  45. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
  46. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  47. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  48. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  49. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  50. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
  51. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/index.ts +37 -0
  52. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
  53. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
  54. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
  55. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
  56. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
  57. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
  58. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
  59. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
  60. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
  61. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
  62. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select.svelte +11 -0
  63. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/index.ts +7 -0
  64. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
  65. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
  66. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  67. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
  68. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
  69. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/index.ts +28 -0
  70. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
  71. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
  72. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
  73. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
  74. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
  75. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
  76. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
  77. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/table/table.svelte +17 -0
  78. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
  79. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  80. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
  81. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
  82. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
  83. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
  84. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  85. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  86. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  87. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  88. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  89. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/i18n/ko.ts +5 -2
  90. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
  91. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +43 -4
  92. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
  93. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
  94. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
  95. package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
  96. package/dist/templates/fullstack-nest-svelte/package.json +11 -3
  97. package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
  98. package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
  99. package/dist/templates/fullstack-nest-svelte/tests/package.json +14 -0
  100. package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
  101. package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
  102. package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +23 -0
  103. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
  104. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +59 -0
  105. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
  106. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
  107. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +40 -0
  108. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
  109. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
  110. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
  111. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
  112. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
  113. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
  114. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
  115. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
  116. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
  117. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
  118. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
  119. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
  120. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
  121. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
  122. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
  123. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
  124. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
  125. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
  126. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
  127. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  128. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
  129. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
  130. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  131. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
  132. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  133. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
  134. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
  135. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
  136. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
  137. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
  138. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
  139. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
  140. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
  141. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
  142. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
  143. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
  144. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
  145. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
  146. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
  147. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
  148. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
  149. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
  150. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
  151. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
  152. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
  153. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
  154. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
  155. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
  156. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
  157. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
  158. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
  159. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
  160. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
  161. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
  162. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
  163. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +359 -0
  164. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +14 -0
  165. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
  166. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +25 -0
  167. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
  168. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +374 -0
  169. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +8 -0
  170. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +78 -0
  171. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +8 -0
  172. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +122 -0
  173. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +9 -0
  174. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +406 -0
  175. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
  176. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +54 -0
  177. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +59 -0
  178. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
  179. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +60 -0
  180. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
  181. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
  182. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
  183. package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +48 -0
  184. package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +43 -0
  185. package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
  186. package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
  187. package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
  188. package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
  189. package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +29 -0
  190. package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -0
  191. package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
  192. package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
  193. package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
  194. package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +21 -0
  195. package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
  196. package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +15 -0
  197. package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
  198. package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +129 -0
  199. package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +19 -0
  200. package/dist/templates/modules/admin-dashboard/module.manifest.json +41 -0
  201. package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.decorator.ts +9 -0
  202. package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.guard.ts +29 -0
  203. package/dist/templates/modules/api-key-auth/files/apps/api/src/api-key/api-key.module.ts +9 -0
  204. package/dist/templates/modules/api-key-auth/files/apps/api/src/machine/machine.controller.ts +14 -0
  205. package/dist/templates/modules/api-key-auth/module.manifest.json +19 -0
  206. package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
  207. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
  208. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +70 -0
  209. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +46 -0
  210. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +21 -0
  211. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
  212. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +54 -0
  213. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +14 -0
  214. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
  215. package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +31 -0
  216. package/dist/templates/modules/audit-log/module.manifest.json +39 -0
  217. package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +30 -0
  218. package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +5 -0
  219. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +59 -0
  220. package/dist/templates/modules/auth/files/apps/api/src/health/health.controller.ts +26 -0
  221. package/dist/templates/modules/auth/module.manifest.json +70 -0
  222. package/dist/templates/modules/bullmq/module.manifest.json +11 -3
  223. package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +29 -0
  224. package/dist/templates/modules/logging/module.manifest.json +27 -0
  225. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +26 -0
  226. package/dist/templates/modules/rate-limit/module.manifest.json +25 -0
  227. package/dist/templates/todo/apps/api/package.json +29 -11
  228. package/dist/templates/todo/apps/api/src/app.module.ts +3 -0
  229. package/dist/templates/todo/apps/api/tsconfig.json +14 -5
  230. package/dist/templates/todo/apps/web/package.json +8 -6
  231. package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
  232. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-action.svelte +20 -0
  233. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-description.svelte +23 -0
  234. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert-title.svelte +23 -0
  235. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/alert.svelte +43 -0
  236. package/dist/templates/todo/apps/web/src/lib/components/ui/alert/index.ts +17 -0
  237. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-badge.svelte +26 -0
  238. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte +20 -0
  239. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group-count.svelte +23 -0
  240. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-group.svelte +23 -0
  241. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar-image.svelte +17 -0
  242. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/avatar.svelte +26 -0
  243. package/dist/templates/todo/apps/web/src/lib/components/ui/avatar/index.ts +22 -0
  244. package/dist/templates/todo/apps/web/src/lib/components/ui/badge/badge.svelte +49 -0
  245. package/dist/templates/todo/apps/web/src/lib/components/ui/badge/index.ts +2 -0
  246. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  247. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-content.svelte +48 -0
  248. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-description.svelte +17 -0
  249. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-footer.svelte +32 -0
  250. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-header.svelte +20 -0
  251. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte +17 -0
  252. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-portal.svelte +7 -0
  253. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-title.svelte +17 -0
  254. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  255. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/dialog.svelte +7 -0
  256. package/dist/templates/todo/apps/web/src/lib/components/ui/dialog/index.ts +34 -0
  257. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  258. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +44 -0
  259. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
  260. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  261. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
  262. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
  263. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
  264. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  265. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  266. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  267. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  268. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  269. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +17 -0
  270. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  271. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  272. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  273. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  274. package/dist/templates/todo/apps/web/src/lib/components/ui/dropdown-menu/index.ts +54 -0
  275. package/dist/templates/todo/apps/web/src/lib/components/ui/select/index.ts +37 -0
  276. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-content.svelte +45 -0
  277. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group-heading.svelte +21 -0
  278. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-group.svelte +17 -0
  279. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-item.svelte +40 -0
  280. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-label.svelte +20 -0
  281. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-portal.svelte +7 -0
  282. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-down-button.svelte +20 -0
  283. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-scroll-up-button.svelte +20 -0
  284. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-separator.svelte +18 -0
  285. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select-trigger.svelte +29 -0
  286. package/dist/templates/todo/apps/web/src/lib/components/ui/select/select.svelte +11 -0
  287. package/dist/templates/todo/apps/web/src/lib/components/ui/separator/index.ts +7 -0
  288. package/dist/templates/todo/apps/web/src/lib/components/ui/separator/separator.svelte +23 -0
  289. package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/index.ts +7 -0
  290. package/dist/templates/todo/apps/web/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
  291. package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/index.ts +1 -0
  292. package/dist/templates/todo/apps/web/src/lib/components/ui/sonner/sonner.svelte +34 -0
  293. package/dist/templates/todo/apps/web/src/lib/components/ui/table/index.ts +28 -0
  294. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-body.svelte +15 -0
  295. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-caption.svelte +20 -0
  296. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-cell.svelte +15 -0
  297. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-footer.svelte +20 -0
  298. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-head.svelte +15 -0
  299. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-header.svelte +20 -0
  300. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table-row.svelte +15 -0
  301. package/dist/templates/todo/apps/web/src/lib/components/ui/table/table.svelte +17 -0
  302. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/index.ts +18 -0
  303. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  304. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +40 -0
  305. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +23 -0
  306. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs.svelte +19 -0
  307. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/index.ts +19 -0
  308. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
  309. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
  310. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
  311. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  312. package/dist/templates/todo/apps/web/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
  313. package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +5 -2
  314. package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
  315. package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +43 -4
  316. package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
  317. package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
  318. package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
  319. package/dist/templates/todo/dot-gitignore +5 -0
  320. package/dist/templates/todo/package.json +11 -3
  321. package/dist/templates/todo/tests/README.md +15 -0
  322. package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
  323. package/dist/templates/todo/tests/package.json +14 -0
  324. package/dist/templates/todo/tests/playwright.config.ts +25 -0
  325. package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
  326. package/package.json +2 -2
  327. package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
  328. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
  329. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.controller.ts +0 -31
  330. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.module.ts +0 -22
  331. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.service.ts +0 -44
  332. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/login.dto.ts +0 -12
  333. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/register.dto.ts +0 -13
  334. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt-auth.guard.ts +0 -5
  335. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt.strategy.ts +0 -23
  336. package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/user.entity.ts +0 -16
  337. package/dist/templates/modules/auth-jwt/files/apps/api/src/migrations/1720200000000-InitUsers.ts +0 -23
  338. package/dist/templates/modules/auth-jwt/module.manifest.json +0 -31
  339. package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
  340. package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
  341. package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
  342. package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import { Tooltip as TooltipPrimitive } from "bits-ui";
3
+
4
+ let { delayDuration = 0, ...restProps }: TooltipPrimitive.ProviderProps = $props();
5
+ </script>
6
+
7
+ <TooltipPrimitive.Provider {delayDuration} {...restProps} />
@@ -0,0 +1,7 @@
1
+ <script lang="ts" generics="T = never">
2
+ import { Tooltip as TooltipPrimitive } from "bits-ui";
3
+
4
+ let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps<T> = $props();
5
+ </script>
6
+
7
+ <TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
@@ -0,0 +1,7 @@
1
+ <script lang="ts" generics="T = never">
2
+ import { Tooltip as TooltipPrimitive } from "bits-ui";
3
+
4
+ let { open = $bindable(false), ...restProps }: TooltipPrimitive.RootProps<T> = $props();
5
+ </script>
6
+
7
+ <TooltipPrimitive.Root bind:open {...restProps} />
@@ -1,8 +1,11 @@
1
- import type { Translation } from "typesafe-i18n";
1
+ import type { BaseTranslation } from "typesafe-i18n";
2
2
 
3
+ // Uses BaseTranslation until you generate typed runtimes with `npx typesafe-i18n`.
4
+ // After generating, switch to `import type { Translation } from "../i18n-types"`
5
+ // so this locale is checked against the base locale's keys.
3
6
  const ko = {
4
7
  appTitle: "{{projectName}}",
5
8
  checkHealth: "API 상태 확인",
6
- } satisfies Translation;
9
+ } satisfies BaseTranslation;
7
10
 
8
11
  export default ko;
@@ -0,0 +1,19 @@
1
+ import type { RequestEvent } from "@sveltejs/kit";
2
+ import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
3
+ import { backendBaseUrl } from "./backend-proxy";
4
+
5
+ // Server-side client for load functions and hooks: points at the internal
6
+ // backend URL and forwards the incoming request's cookies.
7
+ export function serverApiClient(event: RequestEvent): ApiClient {
8
+ const cookie = event.request.headers.get("cookie") ?? "";
9
+ const fetchWithCookies: typeof fetch = (input, init) => {
10
+ const headers = new Headers(init?.headers);
11
+ if (cookie) headers.set("cookie", cookie);
12
+ return event.fetch(input, { ...init, headers });
13
+ };
14
+ return createApiClient({
15
+ baseUrl: backendBaseUrl(),
16
+ apiBasePath: "",
17
+ fetch: fetchWithCookies,
18
+ });
19
+ }
@@ -1,16 +1,55 @@
1
1
  // Server-side proxy boundary. The browser never talks to the API directly:
2
- // only these allowlisted headers are forwarded to BACKEND_INTERNAL_URL.
3
- const FORWARDED_HEADERS = ["authorization", "cookie", "content-type"];
2
+ // requests go through SvelteKit server routes, which forward an allowlist of
3
+ // headers to BACKEND_INTERNAL_URL and relay the response (including Set-Cookie).
4
+ const FORWARDED_HEADERS = ["authorization", "cookie", "content-type", "accept", "origin", "referer"];
5
+ const RELAYED_RESPONSE_HEADERS = ["content-type", "location", "cache-control"];
4
6
 
5
7
  export function backendBaseUrl(): string {
6
8
  return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
7
9
  }
8
10
 
9
- export function backendProxyHeaders(request: Request): Headers {
11
+ // Normalize the resolved client IP for the forwarded header. better-auth stores
12
+ // IPv4 as-is but anonymizes IPv6 (masks the host bits), so an IPv6 loopback would
13
+ // be recorded as "::" — map loopback to 127.0.0.1 for a clean local value, and
14
+ // unwrap IPv4-mapped addresses.
15
+ function normalizeClientIp(address: string | undefined): string | undefined {
16
+ if (!address) return undefined;
17
+ if (address === "::1" || address === "::") return "127.0.0.1";
18
+ if (address.startsWith("::ffff:")) return address.slice("::ffff:".length);
19
+ return address;
20
+ }
21
+
22
+ export async function proxyRequest(
23
+ request: Request,
24
+ targetUrl: string,
25
+ clientAddress?: string,
26
+ ): Promise<Response> {
10
27
  const headers = new Headers();
11
28
  for (const name of FORWARDED_HEADERS) {
12
29
  const value = request.headers.get(name);
13
30
  if (value) headers.set(name, value);
14
31
  }
15
- return headers;
32
+ // Forward the resolved client IP so the API (better-auth) can record it.
33
+ const forwardedIp = normalizeClientIp(clientAddress);
34
+ if (forwardedIp) headers.set("x-forwarded-for", forwardedIp);
35
+
36
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
37
+ const upstream = await fetch(targetUrl, {
38
+ method: request.method,
39
+ headers,
40
+ body: hasBody ? await request.text() : undefined,
41
+ redirect: "manual",
42
+ });
43
+
44
+ const responseHeaders = new Headers();
45
+ for (const name of RELAYED_RESPONSE_HEADERS) {
46
+ const value = upstream.headers.get(name);
47
+ if (value) responseHeaders.set(name, value);
48
+ }
49
+ // Relay Set-Cookie so auth session cookies reach the browser.
50
+ for (const cookie of upstream.headers.getSetCookie()) {
51
+ responseHeaders.append("set-cookie", cookie);
52
+ }
53
+
54
+ return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
16
55
  }
@@ -1,14 +1,19 @@
1
1
  <script lang="ts">
2
2
  import { Button } from "$lib/components/ui/button";
3
3
  import * as Card from "$lib/components/ui/card";
4
+ import { api } from "$lib/api";
5
+ import { ApiError } from "@podosoft/podokit-api-client";
4
6
 
5
7
  type Health = { status: string } | { error: string };
6
8
 
7
9
  let health = $state<Health | null>(null);
8
10
 
9
11
  async function check(): Promise<void> {
10
- const res = await fetch("/api/health");
11
- health = res.ok ? await res.json() : { error: `HTTP ${res.status}` };
12
+ try {
13
+ health = await api.get<{ status: string }>("/health");
14
+ } catch (err) {
15
+ health = { error: err instanceof ApiError ? `${err.code} (${err.statusCode})` : "Request failed" };
16
+ }
12
17
  }
13
18
  </script>
14
19
 
@@ -21,7 +26,7 @@
21
26
  <Card.Root>
22
27
  <Card.Header>
23
28
  <Card.Title>API health</Card.Title>
24
- <Card.Description>Check the NestJS API through the SvelteKit server proxy.</Card.Description>
29
+ <Card.Description>Check the NestJS API through the typed ApiClient.</Card.Description>
25
30
  </Card.Header>
26
31
  <Card.Content class="flex flex-col gap-4">
27
32
  <Button class="w-fit" onclick={check}>Check API health</Button>
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
3
+
4
+ // Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
5
+ const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
6
+ proxyRequest(request, `${backendBaseUrl()}/${params.path}${url.search}`, getClientAddress());
7
+
8
+ export const GET = handler;
9
+ export const POST = handler;
10
+ export const PUT = handler;
11
+ export const PATCH = handler;
12
+ export const DELETE = handler;
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
3
+
4
+ // Proxy the better-auth handler, keeping the /api/auth prefix.
5
+ const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
6
+ proxyRequest(request, `${backendBaseUrl()}/api/auth/${params.all}${url.search}`, getClientAddress());
7
+
8
+ export const GET = handler;
9
+ export const POST = handler;
10
+ export const PUT = handler;
11
+ export const PATCH = handler;
12
+ export const DELETE = handler;
@@ -7,3 +7,8 @@ build/
7
7
  !.env.example
8
8
  .DS_Store
9
9
  *.log
10
+
11
+ # e2e test artifacts
12
+ tests/playwright/
13
+ tests/playwright-report/
14
+ tests/test-results/
@@ -2,12 +2,20 @@
2
2
  "name": "{{projectName}}",
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
- "engines": { "node": ">=20" },
6
- "workspaces": ["apps/*"],
5
+ "engines": {
6
+ "node": ">=20"
7
+ },
8
+ "workspaces": [
9
+ "apps/*",
10
+ "tests"
11
+ ],
7
12
  "scripts": {
8
13
  "dev": "npm run dev --workspaces --if-present",
9
14
  "build": "npm run build --workspaces --if-present",
10
15
  "lint": "npm run lint --workspaces --if-present",
11
- "test": "npm run test --workspaces --if-present"
16
+ "test": "npm run test --workspaces --if-present",
17
+ "test:e2e": "npm run test:e2e -w tests",
18
+ "test:e2e:ui": "npm run test:e2e:ui -w tests",
19
+ "test:e2e:api": "npm run test:e2e:api -w tests"
12
20
  }
13
21
  }
@@ -0,0 +1,15 @@
1
+ # tests
2
+
3
+ End-to-end tests for this app, run with [`@playwright/test`](https://playwright.dev):
4
+
5
+ ```bash
6
+ npm run test:e2e # all specs (api + ui)
7
+ npm run test:e2e:api # backend endpoints (request-only)
8
+ npm run test:e2e:ui # pages (chromium)
9
+ npm run test:e2e:report # open the HTML report
10
+ ```
11
+
12
+ The suite runs against a live stack (web on `E2E_BASE_URL`, default
13
+ `http://localhost:5173`, proxying `/api/*` to the API). Start PostgreSQL, run the
14
+ auth migration, and start the API + web first. Files ending in `*.api.spec.ts` run
15
+ in the request-only `api` project; `*.ui.spec.ts` run in the chromium `ui` project.
@@ -0,0 +1,14 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ // Health endpoints are public and present in every generated app.
4
+ test("GET /health is public and ok @smoke", async ({ request }) => {
5
+ const res = await request.get("/api/health");
6
+ expect(res.ok()).toBeTruthy();
7
+ expect((await res.json()).status).toBe("ok");
8
+ });
9
+
10
+ test("GET /health/ready reports the database", async ({ request }) => {
11
+ const res = await request.get("/api/health/ready");
12
+ expect(res.status()).toBe(200);
13
+ expect((await res.json()).status).toBe("ready");
14
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "{{projectName}}-tests",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "test:e2e": "playwright test",
7
+ "test:e2e:ui": "playwright test --project=ui",
8
+ "test:e2e:api": "playwright test --project=api",
9
+ "test:e2e:report": "playwright show-report"
10
+ },
11
+ "devDependencies": {
12
+ "@playwright/test": "^1.61.1"
13
+ }
14
+ }
@@ -0,0 +1,25 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ // The suite runs against a live stack: web on E2E_BASE_URL (proxying /api/* to
4
+ // the API). Start it first (see docs/testing.md) — locally via the dev harness,
5
+ // in CI via scripts/e2e-ci.mjs. Two projects: `api` (request-only, backend e2e)
6
+ // and `ui` (chromium, pages). Files are routed by suffix (*.api/*.ui.spec.ts).
7
+ const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5173";
8
+
9
+ export default defineConfig({
10
+ testDir: ".",
11
+ fullyParallel: true,
12
+ forbidOnly: !!process.env.CI,
13
+ retries: process.env.CI ? 2 : 0,
14
+ workers: process.env.CI ? 1 : undefined,
15
+ reporter: [["list"], ["html", { open: "never" }]],
16
+ use: {
17
+ baseURL,
18
+ trace: "on-first-retry",
19
+ screenshot: "only-on-failure",
20
+ },
21
+ projects: [
22
+ { name: "api", testMatch: /.*\.api\.spec\.ts/ },
23
+ { name: "ui", testMatch: /.*\.ui\.spec\.ts/, use: { ...devices["Desktop Chrome"] } },
24
+ ],
25
+ });
@@ -0,0 +1,8 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ // Module-agnostic: the app responds at "/" (a page or a redirect to it).
4
+ test("app is reachable @smoke", async ({ page }) => {
5
+ const res = await page.goto("/");
6
+ expect(res?.status()).toBeLessThan(400);
7
+ await expect(page.locator("body")).toBeVisible();
8
+ });
@@ -0,0 +1,23 @@
1
+ import type { Locale } from "$lib/i18n/messages";
2
+
3
+ export type SessionUser = {
4
+ id: string;
5
+ email: string;
6
+ name: string;
7
+ role?: string | null;
8
+ image?: string | null;
9
+ emailVerified?: boolean;
10
+ twoFactorEnabled?: boolean | null;
11
+ };
12
+
13
+ declare global {
14
+ namespace App {
15
+ interface Locals {
16
+ user: SessionUser | null;
17
+ session: { id: string; impersonatedBy?: string | null } | null;
18
+ locale: Locale;
19
+ }
20
+ }
21
+ }
22
+
23
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { Handle } from "@sveltejs/kit";
2
+ import { serverApiClient } from "$lib/server/api";
3
+ import { resolveLocale } from "$lib/i18n/messages";
4
+
5
+ export const handle: Handle = async ({ event, resolve }) => {
6
+ event.locals.locale = resolveLocale(event.cookies.get("locale"));
7
+ try {
8
+ const { data } = await serverApiClient(event).auth.getSession();
9
+ event.locals.user = (data?.user as App.Locals["user"]) ?? null;
10
+ event.locals.session = data?.session
11
+ ? { id: data.session.id, impersonatedBy: (data.session as { impersonatedBy?: string | null }).impersonatedBy ?? null }
12
+ : null;
13
+ } catch {
14
+ event.locals.user = null;
15
+ event.locals.session = null;
16
+ }
17
+ return resolve(event, {
18
+ transformPageChunk: ({ html }) => html.replace('lang="en"', `lang="${event.locals.locale}"`),
19
+ });
20
+ };
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ import { page } from "$app/state";
3
+ import * as Sidebar from "$lib/components/ui/sidebar";
4
+ import SidebarUserMenu from "./sidebar-user-menu.svelte";
5
+ import LayoutDashboardIcon from "@lucide/svelte/icons/layout-dashboard";
6
+ import UsersIcon from "@lucide/svelte/icons/users";
7
+ import MonitorSmartphoneIcon from "@lucide/svelte/icons/monitor-smartphone";
8
+ import ScrollTextIcon from "@lucide/svelte/icons/scroll-text";
9
+ import { getI18n } from "$lib/i18n";
10
+ import type { SessionUser } from "../../app.d.ts";
11
+ import type { Messages } from "$lib/i18n/messages";
12
+ import type { Component } from "svelte";
13
+
14
+ let { user, capabilities }: { user: SessionUser; capabilities?: { auditLog?: boolean } } = $props();
15
+ const i18n = getI18n();
16
+
17
+ type NavItem = { href: string; key: keyof Messages["nav"]; icon: Component; adminOnly?: boolean };
18
+ const items = $derived<NavItem[]>([
19
+ { href: "/admin", key: "overview", icon: LayoutDashboardIcon },
20
+ { href: "/admin/users", key: "users", icon: UsersIcon, adminOnly: true },
21
+ { href: "/admin/sessions", key: "sessions", icon: MonitorSmartphoneIcon, adminOnly: true },
22
+ ...(capabilities?.auditLog
23
+ ? [{ href: "/admin/audit", key: "audit", icon: ScrollTextIcon, adminOnly: true } as NavItem]
24
+ : []),
25
+ ]);
26
+ const visible = $derived(items.filter((item) => !item.adminOnly || user.role === "admin"));
27
+ </script>
28
+
29
+ <Sidebar.Root collapsible="icon">
30
+ <Sidebar.Header>
31
+ <div
32
+ data-testid="sidebar-brand"
33
+ class="flex items-center gap-2 overflow-hidden px-2 py-1 font-semibold whitespace-nowrap group-data-[collapsible=icon]:hidden"
34
+ >
35
+ {i18n.t.common.appName}
36
+ </div>
37
+ </Sidebar.Header>
38
+ <Sidebar.Content>
39
+ <Sidebar.Group>
40
+ <Sidebar.Menu>
41
+ {#each visible as item (item.href)}
42
+ <Sidebar.MenuItem>
43
+ <Sidebar.MenuButton isActive={page.url.pathname === item.href} tooltipContent={i18n.t.nav[item.key]}>
44
+ {#snippet child({ props })}
45
+ <a href={item.href} {...props}>
46
+ <item.icon />
47
+ <span>{i18n.t.nav[item.key]}</span>
48
+ </a>
49
+ {/snippet}
50
+ </Sidebar.MenuButton>
51
+ </Sidebar.MenuItem>
52
+ {/each}
53
+ </Sidebar.Menu>
54
+ </Sidebar.Group>
55
+ </Sidebar.Content>
56
+ <Sidebar.Footer>
57
+ <SidebarUserMenu {user} />
58
+ </Sidebar.Footer>
59
+ </Sidebar.Root>
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import { invalidateAll } from "$app/navigation";
3
+ import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
4
+ import { Button } from "$lib/components/ui/button";
5
+ import GlobeIcon from "@lucide/svelte/icons/globe";
6
+ import { getI18n } from "$lib/i18n";
7
+ import { LOCALES, localeNames, type Locale } from "$lib/i18n/messages";
8
+
9
+ const i18n = getI18n();
10
+
11
+ async function change(locale: Locale): Promise<void> {
12
+ document.cookie = `locale=${locale};path=/;max-age=31536000;samesite=lax`;
13
+ document.documentElement.lang = locale;
14
+ await invalidateAll();
15
+ }
16
+ </script>
17
+
18
+ <DropdownMenu.Root>
19
+ <DropdownMenu.Trigger>
20
+ {#snippet child({ props })}
21
+ <Button {...props} variant="ghost" size="icon" aria-label={i18n.t.language.label}>
22
+ <GlobeIcon class="size-4" />
23
+ </Button>
24
+ {/snippet}
25
+ </DropdownMenu.Trigger>
26
+ <DropdownMenu.Content align="end">
27
+ {#each LOCALES as locale (locale)}
28
+ <DropdownMenu.Item onSelect={() => change(locale)} class={i18n.locale === locale ? "font-medium" : ""}>
29
+ {localeNames[locale]}
30
+ </DropdownMenu.Item>
31
+ {/each}
32
+ </DropdownMenu.Content>
33
+ </DropdownMenu.Root>
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import { goto } from "$app/navigation";
3
+ import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
4
+ import * as Sidebar from "$lib/components/ui/sidebar";
5
+ import UserAvatar from "./user-avatar.svelte";
6
+ import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
7
+ import LogOutIcon from "@lucide/svelte/icons/log-out";
8
+ import UserIcon from "@lucide/svelte/icons/user";
9
+ import { api } from "$lib/api";
10
+ import { getI18n } from "$lib/i18n";
11
+ import type { SessionUser } from "../../app.d.ts";
12
+
13
+ let { user }: { user: SessionUser } = $props();
14
+ const i18n = getI18n();
15
+
16
+ async function signOut(): Promise<void> {
17
+ await api.auth.signOut();
18
+ await goto("/login", { invalidateAll: true });
19
+ }
20
+ </script>
21
+
22
+ <Sidebar.Menu>
23
+ <Sidebar.MenuItem>
24
+ <DropdownMenu.Root>
25
+ <DropdownMenu.Trigger>
26
+ {#snippet child({ props })}
27
+ <Sidebar.MenuButton {...props} size="lg" class="data-[state=open]:bg-sidebar-accent">
28
+ <UserAvatar {user} class="size-8" />
29
+ <div class="flex flex-col text-left text-sm leading-tight">
30
+ <span class="truncate font-medium">{user.name}</span>
31
+ <span class="text-muted-foreground truncate text-xs">{user.email}</span>
32
+ </div>
33
+ <ChevronsUpDownIcon class="ml-auto size-4" />
34
+ </Sidebar.MenuButton>
35
+ {/snippet}
36
+ </DropdownMenu.Trigger>
37
+ <DropdownMenu.Content align="end" side="top" class="w-56">
38
+ <DropdownMenu.Item onSelect={() => goto("/admin/account")}><UserIcon class="mr-2 size-4" /> {i18n.t.nav.account}</DropdownMenu.Item>
39
+ <DropdownMenu.Separator />
40
+ <DropdownMenu.Item onSelect={signOut}><LogOutIcon class="mr-2 size-4" /> {i18n.t.nav.signOut}</DropdownMenu.Item>
41
+ </DropdownMenu.Content>
42
+ </DropdownMenu.Root>
43
+ </Sidebar.MenuItem>
44
+ </Sidebar.Menu>
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import * as Pagination from "$lib/components/ui/pagination";
3
+
4
+ // Shared table footer: a count label plus shadcn-svelte pagination that appears
5
+ // only when the list spans more than one page. Bind `page` (1-based). For
6
+ // server-side lists pass `onPageChange` to refetch; client-side lists just bind.
7
+ type Props = {
8
+ count: number;
9
+ perPage?: number;
10
+ page?: number;
11
+ label?: string;
12
+ onPageChange?: (page: number) => void;
13
+ };
14
+ let { count, perPage = 5, page = $bindable(1), label, onPageChange }: Props = $props();
15
+ </script>
16
+
17
+ <div class="flex items-center justify-between gap-2">
18
+ {#if label}
19
+ <span class="text-muted-foreground shrink-0 text-sm whitespace-nowrap">{label}</span>
20
+ {/if}
21
+ {#if count > perPage}
22
+ <Pagination.Root {count} {perPage} bind:page {onPageChange}>
23
+ {#snippet children({ pages, currentPage })}
24
+ <Pagination.Content>
25
+ <Pagination.Item><Pagination.PrevButton /></Pagination.Item>
26
+ {#each pages as p (p.key)}
27
+ {#if p.type === "ellipsis"}
28
+ <Pagination.Item><Pagination.Ellipsis /></Pagination.Item>
29
+ {:else}
30
+ <Pagination.Item>
31
+ <Pagination.Link page={p} isActive={currentPage === p.value}>{p.value}</Pagination.Link>
32
+ </Pagination.Item>
33
+ {/if}
34
+ {/each}
35
+ <Pagination.Item><Pagination.NextButton /></Pagination.Item>
36
+ </Pagination.Content>
37
+ {/snippet}
38
+ </Pagination.Root>
39
+ {/if}
40
+ </div>
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import { setMode } from "mode-watcher";
3
+ import SunIcon from "@lucide/svelte/icons/sun";
4
+ import MoonIcon from "@lucide/svelte/icons/moon";
5
+ import MonitorIcon from "@lucide/svelte/icons/monitor";
6
+ import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
7
+ import { Button } from "$lib/components/ui/button";
8
+ import { getI18n } from "$lib/i18n";
9
+
10
+ const i18n = getI18n();
11
+ </script>
12
+
13
+ <DropdownMenu.Root>
14
+ <DropdownMenu.Trigger>
15
+ {#snippet child({ props })}
16
+ <Button {...props} variant="ghost" size="icon" aria-label={i18n.t.theme.toggle}>
17
+ <SunIcon class="size-4 dark:hidden" />
18
+ <MoonIcon class="hidden size-4 dark:block" />
19
+ </Button>
20
+ {/snippet}
21
+ </DropdownMenu.Trigger>
22
+ <DropdownMenu.Content align="end">
23
+ <DropdownMenu.Item onSelect={() => setMode("light")}><SunIcon class="mr-2 size-4" /> {i18n.t.theme.light}</DropdownMenu.Item>
24
+ <DropdownMenu.Item onSelect={() => setMode("dark")}><MoonIcon class="mr-2 size-4" /> {i18n.t.theme.dark}</DropdownMenu.Item>
25
+ <DropdownMenu.Item onSelect={() => setMode("system")}><MonitorIcon class="mr-2 size-4" /> {i18n.t.theme.system}</DropdownMenu.Item>
26
+ </DropdownMenu.Content>
27
+ </DropdownMenu.Root>
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import { Collapsible as CollapsiblePrimitive } from "bits-ui";
3
+
4
+ let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.ContentProps = $props();
5
+ </script>
6
+
7
+ <CollapsiblePrimitive.Content bind:ref data-slot="collapsible-content" {...restProps} />
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import { Collapsible as CollapsiblePrimitive } from "bits-ui";
3
+
4
+ let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.TriggerProps = $props();
5
+ </script>
6
+
7
+ <CollapsiblePrimitive.Trigger bind:ref data-slot="collapsible-trigger" {...restProps} />
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { Collapsible as CollapsiblePrimitive } from "bits-ui";
3
+
4
+ let {
5
+ ref = $bindable(null),
6
+ open = $bindable(false),
7
+ ...restProps
8
+ }: CollapsiblePrimitive.RootProps = $props();
9
+ </script>
10
+
11
+ <CollapsiblePrimitive.Root bind:ref bind:open data-slot="collapsible" {...restProps} />
@@ -0,0 +1,13 @@
1
+ import Root from "./collapsible.svelte";
2
+ import Trigger from "./collapsible-trigger.svelte";
3
+ import Content from "./collapsible-content.svelte";
4
+
5
+ export {
6
+ Root,
7
+ Content,
8
+ Trigger,
9
+ //
10
+ Root as Collapsible,
11
+ Content as CollapsibleContent,
12
+ Trigger as CollapsibleTrigger,
13
+ };
@@ -0,0 +1,31 @@
1
+ import Root from "./pagination.svelte";
2
+ import Content from "./pagination-content.svelte";
3
+ import Item from "./pagination-item.svelte";
4
+ import Link from "./pagination-link.svelte";
5
+ import PrevButton from "./pagination-prev-button.svelte";
6
+ import NextButton from "./pagination-next-button.svelte";
7
+ import Ellipsis from "./pagination-ellipsis.svelte";
8
+ import Previous from "./pagination-previous.svelte";
9
+ import Next from "./pagination-next.svelte";
10
+
11
+ export {
12
+ Root,
13
+ Content,
14
+ Item,
15
+ Link,
16
+ PrevButton, // old
17
+ NextButton, // old
18
+ Ellipsis,
19
+ Previous,
20
+ Next,
21
+ //
22
+ Root as Pagination,
23
+ Content as PaginationContent,
24
+ Item as PaginationItem,
25
+ Link as PaginationLink,
26
+ PrevButton as PaginationPrevButton, // old
27
+ NextButton as PaginationNextButton, // old
28
+ Ellipsis as PaginationEllipsis,
29
+ Previous as PaginationPrevious,
30
+ Next as PaginationNext,
31
+ };