@0x0501/mystack 0.0.0-stage → 0.1.0-bootstrap.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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -2
  3. package/assets/auth.sqlite.schema.ts +479 -0
  4. package/assets/bun-sqlite.test.ts +46 -0
  5. package/assets/d1.test.ts +41 -0
  6. package/assets/drizzle.config.ts +2 -0
  7. package/assets/generate-auth-schema.mjs +25 -0
  8. package/assets/platform.sqlite.schema.ts +46 -0
  9. package/assets/polar-checkout.ts +10 -0
  10. package/assets/polar-route.ts +19 -0
  11. package/assets/polar.test.ts +30 -0
  12. package/assets/polar.ts +12 -0
  13. package/assets/purchase.bun-sqlite.ts +38 -0
  14. package/assets/purchase.sqlite.ts +39 -0
  15. package/assets/sqlite-migrations/0000_thin_phalanx.sql +295 -0
  16. package/assets/sqlite-migrations/meta/0000_snapshot.json +2058 -0
  17. package/assets/sqlite-migrations/meta/_journal.json +13 -0
  18. package/assets/turso.test.ts +40 -0
  19. package/package.json +44 -5
  20. package/src/adapters/database.mjs +448 -0
  21. package/src/adapters/deployment.mjs +101 -0
  22. package/src/adapters/email.mjs +75 -0
  23. package/src/adapters/payments.mjs +270 -0
  24. package/src/catalog.mjs +128 -0
  25. package/src/cli.mjs +294 -0
  26. package/src/generate.mjs +183 -0
  27. package/src/template.mjs +93 -0
  28. package/template/.env.docker +16 -0
  29. package/template/.env.example +67 -0
  30. package/template/DESIGN.md +58 -0
  31. package/template/LICENSE +21 -0
  32. package/template/biome.json +46 -0
  33. package/template/docker-compose.yml +47 -0
  34. package/template/docs/project-guide.md +61 -0
  35. package/template/drizzle/0000_create-app-role.sql +54 -0
  36. package/template/drizzle/0001_add-rls-context.sql +21 -0
  37. package/template/drizzle/0002_platform_tables.sql +320 -0
  38. package/template/drizzle/meta/0002_snapshot.json +2125 -0
  39. package/template/drizzle/meta/_journal.json +27 -0
  40. package/template/drizzle.config.ts +25 -0
  41. package/template/e2e/authenticated-ui.spec.ts +40 -0
  42. package/template/e2e/headless-design.spec.ts +66 -0
  43. package/template/e2e/navigation-loading.spec.ts +70 -0
  44. package/template/e2e/theme-responsive.spec.ts +260 -0
  45. package/template/gitignore +24 -0
  46. package/template/messages/de.json +212 -0
  47. package/template/messages/en.json +212 -0
  48. package/template/package.json +101 -0
  49. package/template/paraglide.config.ts +49 -0
  50. package/template/playwright.config.ts +58 -0
  51. package/template/project.inlang/settings.json +12 -0
  52. package/template/public/drizzle.svg +1 -0
  53. package/template/public/favicon.ico +0 -0
  54. package/template/public/logo192.png +0 -0
  55. package/template/public/logo512.png +0 -0
  56. package/template/public/manifest.json +25 -0
  57. package/template/public/robots.txt +3 -0
  58. package/template/scripts/check-client-bundle.ts +150 -0
  59. package/template/scripts/check-server-fns.ts +290 -0
  60. package/template/scripts/check-sql-raw.ts +50 -0
  61. package/template/scripts/compile-paraglide.ts +12 -0
  62. package/template/scripts/postgres/README.md +76 -0
  63. package/template/scripts/postgres/create-app-role.sh +60 -0
  64. package/template/scripts/seed-local.ts +331 -0
  65. package/template/scripts/validate-prod-env.ts +53 -0
  66. package/template/src/__tests__/README.md +9 -0
  67. package/template/src/__tests__/account-security.test.ts +664 -0
  68. package/template/src/__tests__/admin-http-surface.test.ts +311 -0
  69. package/template/src/__tests__/admin-peer-protection.test.ts +178 -0
  70. package/template/src/__tests__/auth-harness.ts +104 -0
  71. package/template/src/__tests__/auth-rate-limit.test.ts +43 -0
  72. package/template/src/__tests__/auth-redirect.test.ts +120 -0
  73. package/template/src/__tests__/body-limit.test.ts +47 -0
  74. package/template/src/__tests__/creem.test.ts +62 -0
  75. package/template/src/__tests__/demo-checkout.test.ts +141 -0
  76. package/template/src/__tests__/email-locale.test.ts +110 -0
  77. package/template/src/__tests__/global-setup.ts +80 -0
  78. package/template/src/__tests__/input-rules.test.ts +0 -0
  79. package/template/src/__tests__/message-parity.test.ts +38 -0
  80. package/template/src/__tests__/navigation-loading.test.tsx +252 -0
  81. package/template/src/__tests__/nowpayments.test.ts +106 -0
  82. package/template/src/__tests__/paraglide-url-contract.test.ts +87 -0
  83. package/template/src/__tests__/purchase-fulfillment.test.ts +204 -0
  84. package/template/src/__tests__/query-unauthorized-redirect.test.ts +95 -0
  85. package/template/src/__tests__/rls-policy.test.ts +120 -0
  86. package/template/src/__tests__/same-page-locale-href.test.ts +47 -0
  87. package/template/src/__tests__/search-params-oauth.test.ts +57 -0
  88. package/template/src/__tests__/security-headers.test.ts +12 -0
  89. package/template/src/__tests__/server-fn-error-contract.test.ts +173 -0
  90. package/template/src/__tests__/stripe.test.ts +84 -0
  91. package/template/src/__tests__/test-db.ts +32 -0
  92. package/template/src/__tests__/test-rls-db.ts +23 -0
  93. package/template/src/__tests__/user-update-guard.test.ts +109 -0
  94. package/template/src/components/LocaleSwitcher.tsx +79 -0
  95. package/template/src/components/ThemeToggle.tsx +47 -0
  96. package/template/src/components/auth/social.tsx +90 -0
  97. package/template/src/components/auth/turnstile.tsx +67 -0
  98. package/template/src/components/root/navigation-progress.tsx +32 -0
  99. package/template/src/components/root/not-found.tsx +20 -0
  100. package/template/src/components/root/pending.tsx +9 -0
  101. package/template/src/components/root/root-document.tsx +37 -0
  102. package/template/src/components/root/root-error.tsx +34 -0
  103. package/template/src/components/shells.tsx +201 -0
  104. package/template/src/components/ui/styles.ts +9 -0
  105. package/template/src/db/auth.schema.ts +455 -0
  106. package/template/src/db/helper.ts +38 -0
  107. package/template/src/db/index.ts +15 -0
  108. package/template/src/db/platform.schema.ts +122 -0
  109. package/template/src/db/schema.ts +2 -0
  110. package/template/src/env.ts +65 -0
  111. package/template/src/hooks/use-theme.ts +32 -0
  112. package/template/src/integrations/better-auth/header-user.tsx +36 -0
  113. package/template/src/integrations/better-auth/session-query.ts +11 -0
  114. package/template/src/integrations/react-email/PasswordOtpEmail.tsx +62 -0
  115. package/template/src/integrations/react-email/ResetPasswordEmail.tsx +64 -0
  116. package/template/src/integrations/react-email/VerifyEmail.tsx +72 -0
  117. package/template/src/integrations/react-email/theme.ts +75 -0
  118. package/template/src/integrations/tanstack-query/devtools.tsx +6 -0
  119. package/template/src/integrations/tanstack-query/root-provider.tsx +60 -0
  120. package/template/src/lib/auth-client.ts +27 -0
  121. package/template/src/lib/auth-paths.ts +94 -0
  122. package/template/src/lib/auth-plugins/existing-email-signup.ts +86 -0
  123. package/template/src/lib/auth-plugins/field-rules.ts +84 -0
  124. package/template/src/lib/auth-plugins/social-two-factor.ts +194 -0
  125. package/template/src/lib/auth-timing.ts +9 -0
  126. package/template/src/lib/auth.ts +279 -0
  127. package/template/src/lib/captcha.ts +11 -0
  128. package/template/src/lib/email.tsx +162 -0
  129. package/template/src/lib/github.ts +18 -0
  130. package/template/src/lib/locale-cookie.ts +12 -0
  131. package/template/src/lib/localized-pages.ts +25 -0
  132. package/template/src/lib/oauth-metadata.ts +19 -0
  133. package/template/src/lib/permissions.ts +29 -0
  134. package/template/src/lib/security-headers.ts +20 -0
  135. package/template/src/lib/site.ts +59 -0
  136. package/template/src/lib/theme.ts +42 -0
  137. package/template/src/lib/user-update-guard.ts +35 -0
  138. package/template/src/middlewares/admin.ts +46 -0
  139. package/template/src/middlewares/auth-rate-limit.ts +37 -0
  140. package/template/src/middlewares/better-auth.ts +15 -0
  141. package/template/src/middlewares/body-limit.ts +62 -0
  142. package/template/src/middlewares/database.ts +13 -0
  143. package/template/src/middlewares/protected.ts +45 -0
  144. package/template/src/routeTree.gen.ts +536 -0
  145. package/template/src/router.tsx +53 -0
  146. package/template/src/routes/[.]well-known/oauth-authorization-server/api/auth.ts +18 -0
  147. package/template/src/routes/[.]well-known/oauth-protected-resource/index.ts +32 -0
  148. package/template/src/routes/__root.tsx +175 -0
  149. package/template/src/routes/admin/audit.tsx +74 -0
  150. package/template/src/routes/admin/index.tsx +7 -0
  151. package/template/src/routes/admin/oauth-apps.tsx +164 -0
  152. package/template/src/routes/admin/route.tsx +27 -0
  153. package/template/src/routes/admin/users.tsx +215 -0
  154. package/template/src/routes/api/auth/$.ts +14 -0
  155. package/template/src/routes/api/auth/[.]well-known/openid-configuration.ts +15 -0
  156. package/template/src/routes/api/webhooks/creem.ts +69 -0
  157. package/template/src/routes/api/webhooks/nowpayments.ts +62 -0
  158. package/template/src/routes/api/webhooks/stripe.ts +74 -0
  159. package/template/src/routes/dashboard/account.tsx +430 -0
  160. package/template/src/routes/dashboard/index.tsx +128 -0
  161. package/template/src/routes/dashboard/route.tsx +17 -0
  162. package/template/src/routes/forgot-password.tsx +65 -0
  163. package/template/src/routes/index.tsx +85 -0
  164. package/template/src/routes/oauth/consent.tsx +77 -0
  165. package/template/src/routes/reset-password.tsx +78 -0
  166. package/template/src/routes/sign-in.tsx +174 -0
  167. package/template/src/routes/sign-up.tsx +135 -0
  168. package/template/src/routes/two-factor.tsx +74 -0
  169. package/template/src/server/README.md +3 -0
  170. package/template/src/server/account-security.ts +163 -0
  171. package/template/src/server/admin-audit.ts +37 -0
  172. package/template/src/server/admin-users.ts +112 -0
  173. package/template/src/server/auth.ts +44 -0
  174. package/template/src/server/demo-checkout.ts +67 -0
  175. package/template/src/server/oauth-clients.ts +83 -0
  176. package/template/src/server/payment-toggles.ts +39 -0
  177. package/template/src/server.ts +20 -0
  178. package/template/src/services/README.md +3 -0
  179. package/template/src/services/account-security.ts +893 -0
  180. package/template/src/services/admin-security.ts +164 -0
  181. package/template/src/services/admin-users.ts +181 -0
  182. package/template/src/services/auth-rate-limit.ts +32 -0
  183. package/template/src/services/creem.ts +83 -0
  184. package/template/src/services/demo-checkout.ts +100 -0
  185. package/template/src/services/nowpayments.ts +154 -0
  186. package/template/src/services/oauth-client.ts +85 -0
  187. package/template/src/services/oauth-clients.ts +15 -0
  188. package/template/src/services/payment-providers.ts +157 -0
  189. package/template/src/services/payment-toggles.ts +48 -0
  190. package/template/src/services/purchase.ts +233 -0
  191. package/template/src/services/stripe.ts +106 -0
  192. package/template/src/start.ts +35 -0
  193. package/template/src/styles/layout.css +93 -0
  194. package/template/src/styles/theme.css +84 -0
  195. package/template/src/styles/ui.css +233 -0
  196. package/template/src/styles.css +90 -0
  197. package/template/src/utils/api-error.ts +129 -0
  198. package/template/src/utils/auth-errors.ts +157 -0
  199. package/template/src/utils/auth-redirect.ts +61 -0
  200. package/template/src/utils/body-limit.ts +84 -0
  201. package/template/src/utils/cn.ts +6 -0
  202. package/template/src/utils/input-rules.ts +172 -0
  203. package/template/src/utils/locale-paths.ts +18 -0
  204. package/template/src/utils/same-page-locale-href.ts +20 -0
  205. package/template/src/utils/search-params.ts +71 -0
  206. package/template/tsconfig.json +30 -0
  207. package/template/tsr.config.json +6 -0
  208. package/template/vite.config.ts +24 -0
  209. package/template/vitest.config.ts +26 -0
  210. package/template/worker-configuration.d.ts +14660 -0
  211. package/template/wrangler.jsonc +39 -0
@@ -0,0 +1,58 @@
1
+ # Headless design
2
+
3
+ The Starter separates platform behavior from presentation. **Headless is an architecture, not a visual style.** Minimalism, expressive branding, dense dashboards, gradients, illustrations, and rich motion are all valid product choices.
4
+
5
+ ## Ownership
6
+
7
+ | Layer | Owns | Customize here |
8
+ |---|---|---|
9
+ | Platform | Authentication, permissions, RLS, OAuth, locale routing, server functions | `src/services/`, `src/server/`, middleware |
10
+ | Interaction | Focus, keyboard navigation, selection, popup lifecycle, ARIA | Compose `@base-ui/react` parts directly |
11
+ | Theme behavior | Stored light/dark/system preference and system changes | `src/hooks/use-theme.ts`; render your own control |
12
+ | Theme values | Colors, typography, radii, light/dark palettes | `src/styles/theme.css` |
13
+ | Presentation | Controls, states, optional motion | `src/styles/ui.css` |
14
+ | Layout | Auth, dashboard, admin, page widths | `src/styles/layout.css`, `src/components/shells.tsx` |
15
+
16
+ The supplied pages are working examples. Their default appearance is replaceable. New Base UI controls have no Starter styles until a presentation class is explicitly applied. Use native HTML for ordinary buttons, links, and inputs; use Base UI for composite interactions. Do not add pass-through wrappers or a second interaction library to restyle an existing control.
17
+
18
+ ## Apply a visual identity
19
+
20
+ Edit the light and dark variables in `theme.css` to change the whole example UI. Semantic names such as `primary`, `muted`, and `destructive` describe presentation roles; they do not prescribe a palette. Include your font assets and change `--font-sans-stack` for typography.
21
+
22
+ For deeper changes, edit or replace `ui.css` and `layout.css` at their imports in `src/styles.css`. Their rules live in `@layer components`, below Tailwind utilities, so per-page utility classes override the supplied appearance without class-merging tricks. Shared class names in `src/components/ui/styles.ts` are presentation hooks, not component behavior.
23
+
24
+ For example, this changes an action's radius and padding:
25
+
26
+ ```tsx
27
+ <button className={`${buttonPrimaryClass} rounded-full px-6`} type="submit">
28
+ Save
29
+ </button>
30
+ ```
31
+
32
+ Shells accept ordinary div props and `className`. Change a shell layout or replace it while retaining the page's platform flow. `useTheme()` returns `preference`, `setTheme`, and `mounted` for a custom theme control; the initial preference is `system` on both server and client, and stored preference is read after hydration. Keep the head initialization script for flash-free theme application.
33
+
34
+ ## Motion
35
+
36
+ Animations are allowed throughout the app: page transitions, enter/exit effects, hover feedback, theme controls, and decorative sequences. Use CSS transitions/keyframes or add Motion/GSAP when the product needs them. No animation library is required by the Starter.
37
+
38
+ - Base UI popups expose `data-starting-style` and `data-ending-style` and positioning variables such as `--transform-origin`. The default menu/select transitions demonstrate this lifecycle.
39
+ - Base UI's native `render`, controlled state, portal, and actions APIs remain available for JavaScript animation. Follow the installed component's lifecycle for exit animations so focus restoration and unmounting still complete.
40
+ - Provide a reduced-motion treatment per animated component using `prefers-reduced-motion`, Tailwind `motion-safe`/`motion-reduce`, or the animation library's equivalent. The Starter does not globally force every animation to a near-zero duration.
41
+ - Keep initial rendering deterministic for SSR; read browser state after hydration. Focus visibility, keyboard access, labels, and readable contrast remain requirements for every skin.
42
+
43
+ ## Contracts while customizing
44
+
45
+ Visual changes must preserve signed OAuth query forwarding, real same-page locale links (path/query/hash), auth capability parity, server authorization, and error/pending feedback. New product visuals and motion do not need to match a reference platform's appearance. Platform behavior still follows the contracts in `CONTEXT.md` and the ADRs.
46
+
47
+ ## Reference choices
48
+
49
+ Reviewed on 2026-10-09. These are organizational references, not runtime dependencies or copied implementations.
50
+
51
+ | Reference | Practice adopted |
52
+ |---|---|
53
+ | [Base UI styling](https://base-ui.com/react/handbook/styling) and [animation](https://base-ui.com/react/handbook/animation) | Unstyled interactions, explicit presentation, native state and lifecycle hooks |
54
+ | [Cove Stack](https://github.com/mugnavo/cove) | Working TanStack Start/Query/Better Auth/Drizzle scaffold with documented environment and customization points |
55
+ | [create-t3-turbo](https://github.com/t3-oss/create-t3-turbo) | Clear ownership of app, auth, data, and UI; applied here as directories in the existing standalone app |
56
+ | [TanStack Start examples](https://tanstack.com/start/latest/docs/framework/react/getting-started) | Framework-native routing and full-stack foundations rather than a replacement application framework |
57
+
58
+ Keep the existing Bun, Workers, Hyperdrive, and platform contracts. Add dependencies only for capabilities used by the actual product.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Darling
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": [
11
+ "**/src/**/*",
12
+ "**/.vscode/**/*",
13
+ "**/index.html",
14
+ "**/vite.config.ts",
15
+ "!**/src/routeTree.gen.ts",
16
+ "!**/src/paraglide"
17
+ ]
18
+ },
19
+ "formatter": {
20
+ "enabled": true,
21
+ "indentStyle": "tab"
22
+ },
23
+ "assist": {
24
+ "actions": {
25
+ "source": {
26
+ "organizeImports": "on"
27
+ }
28
+ }
29
+ },
30
+ "linter": {
31
+ "enabled": true,
32
+ "rules": {
33
+ "preset": "recommended"
34
+ }
35
+ },
36
+ "javascript": {
37
+ "formatter": {
38
+ "quoteStyle": "double"
39
+ }
40
+ },
41
+ "css": {
42
+ "parser": {
43
+ "tailwindDirectives": true
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,47 @@
1
+ # Local Postgres for TanStack Starter development.
2
+ # Usage: bun run db:up / bun run db:down
3
+ #
4
+ # Credentials live in .env.docker (committed dev defaults).
5
+ # Mirror the same passwords in .env (DATABASE_MIGRATION_URL) and
6
+ # wrangler.jsonc hyperdrive.localConnectionString.
7
+
8
+ services:
9
+ postgres:
10
+ image: postgres:17
11
+ container_name: tanstack-starter-db
12
+ restart: unless-stopped
13
+
14
+ env_file:
15
+ - .env.docker
16
+
17
+ environment:
18
+ POSTGRES_USER: ${POSTGRES_USER}
19
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
20
+ POSTGRES_DB: ${POSTGRES_DB}
21
+ PGDATA: /var/lib/postgresql/data/pgdata
22
+
23
+ # runtime db user (read by create-app-role.sh)
24
+ APP_DB_USER: ${APP_DB_USER}
25
+ APP_DB_PASSWORD: ${APP_DB_PASSWORD}
26
+
27
+ # Host port (POSTGRES_PORT) → container 5432 (Postgres always listens on 5432 inside).
28
+ ports:
29
+ - "${POSTGRES_PORT:-5432}:5432"
30
+
31
+ volumes:
32
+ - tanstack_starter_data:/var/lib/postgresql/data
33
+ - ./scripts/postgres:/docker-entrypoint-initdb.d:ro
34
+
35
+ healthcheck:
36
+ test:
37
+ [
38
+ "CMD-SHELL",
39
+ 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"',
40
+ ]
41
+ interval: 5s
42
+ timeout: 5s
43
+ retries: 10
44
+ start_period: 10s
45
+
46
+ volumes:
47
+ tanstack_starter_data:
@@ -0,0 +1,61 @@
1
+ # Work in a Mystack project
2
+
3
+ Your project was generated by `mystack create`. Start with the root README: it lists your database, runtime, email and payments, with setup commands for that configuration. `mystack.json` is a record of creation choices; editing it does not change application source or dependencies.
4
+
5
+ ## Local configuration
6
+
7
+ Use Bun for application commands. Copy `.env.example` to `.env` and configure the selected database. Run `bun run db:migrate` before `bun run dev`. PostgreSQL variants also include `db:up` for local Docker and `db:seed` for development users. SQLite variants use normal registration and omit PostgreSQL container/seed scripts.
8
+
9
+ Open `http://localhost:3000`, matching `APP_ORIGIN` and `BETTER_AUTH_URL`. Authentication email is logged in development when provider credentials are absent. Set a verified sender and real credentials for production. Turnstile and GitHub sign-in are enabled when their required environment values are set.
10
+
11
+ Migration credentials and runtime credentials have different purposes. PostgreSQL's `DATABASE_MIGRATION_URL` is privileged; runtime access uses an app role via `DATABASE_URL` or the `HYPERDRIVE` binding. D1 uses its `DB` binding, Turso Cloud needs its remote URL/token, and local libSQL/Bun SQLite use a file. Keep file databases on persistent storage in production.
12
+
13
+ ## Application structure
14
+
15
+ | Path | Responsibility |
16
+ |---|---|
17
+ | `src/routes/` | TanStack Router pages and HTTP endpoints |
18
+ | `src/server/` | Thin TanStack Start server functions |
19
+ | `src/services/` | Framework-independent business and domain logic |
20
+ | `src/middlewares/` | Database, authentication and authorization boundaries |
21
+ | `src/db/` | Selected database driver, generated auth schema and platform tables |
22
+ | `src/lib/auth.ts`, `src/lib/auth-client.ts` | Better Auth server/client configuration |
23
+ | `src/lib/email.tsx`, `src/lib/email-transport.ts` | React Email rendering and selected transport |
24
+ | `src/styles/`, `DESIGN.md` | Replaceable presentation and design guidance |
25
+ | `messages/`, `project.inlang/settings.json` | Paraglide messages and locale configuration |
26
+ | `src/__tests__/`, `e2e/` | Vitest and Playwright checks |
27
+ | `drizzle/`, `scripts/` | Migrations and application hygiene scripts |
28
+
29
+ ## Add your product
30
+
31
+ Put domain logic in `src/services/` and product tables in the hand-maintained platform schema or new schema modules exported by `src/db/schema.ts`. Keep server functions in `src/server/`; state-changing functions use `method: "POST"` and validate input. Route changes require `bun run generate-routes` and committing the generated tree.
32
+
33
+ PostgreSQL work that respects RLS uses `withRlsUser` / `withRlsService`. SQLite uses `withDatabaseUser` / `withDatabaseService` as database scopes, plus explicit ownership predicates and authenticated/admin middleware; those helpers do not provide RLS. Preserve the authorization checks when adding tables or queries.
34
+
35
+ Better Auth is an OAuth/OIDC issuer as well as supporting account sign-in. Preserve signed OAuth search parameters, issuer metadata, the consent query and disabled HTTP paths when customizing authentication. Keep `tanstackStartCookies()` last. After plugin or field changes, run `bun run auth:generate`, then `bun run db:generate` and `bun run db:migrate`. Never edit `src/db/auth.schema.ts` by hand.
36
+
37
+ Payment integrations record a Purchase and expose the purchase-paid extension point. PostgreSQL fulfillment runs database work inside the paid-state transaction; external IO needs a durable row processed afterward. SQLite atomically writes `purchase_delivery` with the paid state. Implement a consumer using `purchase_id` as its idempotency key for product fulfillment. The starter supplies no wallet, subscription or entitlement domain.
38
+
39
+ Compose Base UI directly and customize the CSS, layouts, typography and motion. See [DESIGN.md](../DESIGN.md). Preserve focus, reduced-motion behavior and SSR hydration. Add locales through Paraglide and keep user-facing pages localized in both messages and URLs; API/webhook routes stay unprefixed.
40
+
41
+ ## Validate and deploy
42
+
43
+ ```sh
44
+ bun run check
45
+ bun run typecheck
46
+ bun run test:unit
47
+ bun run build
48
+ bun run check:client-bundle
49
+ ```
50
+
51
+ PostgreSQL integration suites use `bun run test` and require Docker. Use `bun run test:e2e` for browser checks with an application running on its configured origin. Native Bun SQLite has its own `bun test` database checks in addition to Vitest.
52
+
53
+ For production, use the root README's instructions for your selected runtime. `bun run deploy` validates production configuration and checks the client bundle. Workers deploy through Wrangler with `.env.prod`; Nitro and Bun build for a host that injects the production environment, then run `bun run start`. Apply migrations against the production database before starting the new server; D1 hosted migrations use `bun run db:migrate:remote`.
54
+
55
+ Supply real application origins, a new auth secret, database credentials/resource IDs, email credentials and selected webhook signature secrets. Auth registration creates ordinary users; promote the first administrator through an operator-controlled database action, never open registration. For PostgreSQL, after creating the normal user:
56
+
57
+ ```sql
58
+ UPDATE "user" SET role = 'superadmin' WHERE email = 'you@example.com';
59
+ ```
60
+
61
+ Use the equivalent SQL through your selected database's administrative tool for SQLite. Each generated application is owned and maintained independently; the CLI is a creation tool, not an upgrade or runtime dependency.
@@ -0,0 +1,54 @@
1
+ -- App role for Hyperdrive connections. NOBYPASSRLS so policies always apply.
2
+ -- Local Docker: also created by scripts/postgres/create-app-role.sh on first boot
3
+ -- (same name/password as .env.docker). This migration stays idempotent for
4
+ -- hosted Postgres where the Docker init script never ran.
5
+ -- Password below is a local-dev default; production must use a strong secret.
6
+
7
+ DO $$
8
+ BEGIN
9
+ IF NOT EXISTS (
10
+ SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'starter_app'
11
+ ) THEN
12
+ CREATE ROLE starter_app
13
+ LOGIN
14
+ PASSWORD 'starter_app_dev_password'
15
+ NOSUPERUSER
16
+ NOCREATEDB
17
+ NOCREATEROLE
18
+ NOINHERIT
19
+ NOBYPASSRLS;
20
+ END IF;
21
+ END $$;
22
+
23
+ -- Grant CONNECT on the current database (works for `starter`, `postgres`, …)
24
+ DO $$
25
+ BEGIN
26
+ EXECUTE format(
27
+ 'GRANT CONNECT ON DATABASE %I TO starter_app',
28
+ current_database()
29
+ );
30
+ END $$;
31
+
32
+ GRANT USAGE ON SCHEMA public TO starter_app;
33
+
34
+ GRANT SELECT, INSERT, UPDATE, DELETE
35
+ ON ALL TABLES IN SCHEMA public
36
+ TO starter_app;
37
+
38
+ GRANT USAGE, SELECT, UPDATE
39
+ ON ALL SEQUENCES IN SCHEMA public
40
+ TO starter_app;
41
+
42
+ ALTER DEFAULT PRIVILEGES
43
+ FOR ROLE postgres
44
+ IN SCHEMA public
45
+ GRANT SELECT, INSERT, UPDATE, DELETE
46
+ ON TABLES
47
+ TO starter_app;
48
+
49
+ ALTER DEFAULT PRIVILEGES
50
+ FOR ROLE postgres
51
+ IN SCHEMA public
52
+ GRANT USAGE, SELECT, UPDATE
53
+ ON SEQUENCES
54
+ TO starter_app;
@@ -0,0 +1,21 @@
1
+ -- RLS context helpers. is_admin() is created in 0002 after "user" exists
2
+ -- (SQL-language functions validate relation names at CREATE time).
3
+
4
+ CREATE SCHEMA IF NOT EXISTS app_private;
5
+
6
+ REVOKE ALL ON SCHEMA app_private FROM PUBLIC;
7
+
8
+ GRANT USAGE ON SCHEMA app_private TO starter_app;
9
+
10
+ CREATE OR REPLACE FUNCTION app_private.current_user_id()
11
+ RETURNS text
12
+ LANGUAGE sql
13
+ STABLE
14
+ SECURITY INVOKER
15
+ SET search_path = pg_catalog
16
+ AS $$
17
+ SELECT NULLIF(current_setting('app.user_id', true), '');
18
+ $$;
19
+
20
+ REVOKE ALL ON FUNCTION app_private.current_user_id() FROM PUBLIC;
21
+ GRANT EXECUTE ON FUNCTION app_private.current_user_id() TO starter_app;
@@ -0,0 +1,320 @@
1
+ -- Table and policy DDL generated by drizzle-kit from the configured schema.
2
+ -- The is_admin() function must precede the generated policies that call it.
3
+ CREATE TABLE "account" (
4
+ "id" text PRIMARY KEY NOT NULL,
5
+ "account_id" text NOT NULL,
6
+ "provider_id" text NOT NULL,
7
+ "user_id" text NOT NULL,
8
+ "access_token" text,
9
+ "refresh_token" text,
10
+ "id_token" text,
11
+ "access_token_expires_at" timestamp,
12
+ "refresh_token_expires_at" timestamp,
13
+ "scope" text,
14
+ "password" text,
15
+ "created_at" timestamp DEFAULT now() NOT NULL,
16
+ "updated_at" timestamp NOT NULL
17
+ );
18
+ --> statement-breakpoint
19
+ CREATE TABLE "jwks" (
20
+ "id" text PRIMARY KEY NOT NULL,
21
+ "public_key" text NOT NULL,
22
+ "private_key" text NOT NULL,
23
+ "created_at" timestamp NOT NULL,
24
+ "expires_at" timestamp,
25
+ "alg" text,
26
+ "crv" text
27
+ );
28
+ --> statement-breakpoint
29
+ CREATE TABLE "oauth_access_token" (
30
+ "id" text PRIMARY KEY NOT NULL,
31
+ "token" text NOT NULL,
32
+ "client_id" text NOT NULL,
33
+ "session_id" text,
34
+ "user_id" text,
35
+ "reference_id" text,
36
+ "authorization_code_id" text,
37
+ "resources" text[],
38
+ "requested_user_info_claims" text[],
39
+ "refresh_id" text,
40
+ "expires_at" timestamp NOT NULL,
41
+ "created_at" timestamp NOT NULL,
42
+ "revoked" timestamp,
43
+ "confirmation" jsonb,
44
+ "scopes" text[] NOT NULL,
45
+ CONSTRAINT "oauth_access_token_token_unique" UNIQUE("token")
46
+ );
47
+ --> statement-breakpoint
48
+ CREATE TABLE "oauth_client" (
49
+ "id" text PRIMARY KEY NOT NULL,
50
+ "client_id" text NOT NULL,
51
+ "client_secret" text,
52
+ "client_discovery_id" text,
53
+ "disabled" boolean DEFAULT false,
54
+ "skip_consent" boolean,
55
+ "enable_end_session" boolean,
56
+ "subject_type" text,
57
+ "scopes" text[],
58
+ "client_credentials_scopes" text[] DEFAULT '{}',
59
+ "user_id" text,
60
+ "created_at" timestamp,
61
+ "updated_at" timestamp,
62
+ "name" text,
63
+ "uri" text,
64
+ "icon" text,
65
+ "contacts" text[],
66
+ "tos" text,
67
+ "policy" text,
68
+ "software_id" text,
69
+ "software_version" text,
70
+ "software_statement" text,
71
+ "redirect_uris" text[] NOT NULL,
72
+ "post_logout_redirect_uris" text[],
73
+ "backchannel_logout_uri" text,
74
+ "backchannel_logout_session_required" boolean,
75
+ "token_endpoint_auth_method" text,
76
+ "application_type" text,
77
+ "jwks" text,
78
+ "jwks_uri" text,
79
+ "grant_types" text[],
80
+ "response_types" text[],
81
+ "require_pkce" boolean,
82
+ "dpop_bound_access_tokens" boolean DEFAULT false,
83
+ "reference_id" text,
84
+ "metadata" jsonb,
85
+ CONSTRAINT "oauth_client_client_id_unique" UNIQUE("client_id")
86
+ );
87
+ --> statement-breakpoint
88
+ CREATE TABLE "oauth_client_assertion" (
89
+ "id" text PRIMARY KEY NOT NULL,
90
+ "expires_at" timestamp NOT NULL
91
+ );
92
+ --> statement-breakpoint
93
+ CREATE TABLE "oauth_client_resource" (
94
+ "id" text PRIMARY KEY NOT NULL,
95
+ "client_id" text NOT NULL,
96
+ "resource_id" text NOT NULL,
97
+ "metadata" jsonb,
98
+ "created_at" timestamp
99
+ );
100
+ --> statement-breakpoint
101
+ CREATE TABLE "oauth_consent" (
102
+ "id" text PRIMARY KEY NOT NULL,
103
+ "client_id" text NOT NULL,
104
+ "user_id" text,
105
+ "reference_id" text,
106
+ "resources" text[],
107
+ "requested_user_info_claims" text[],
108
+ "scopes" text[] NOT NULL,
109
+ "created_at" timestamp NOT NULL,
110
+ "updated_at" timestamp NOT NULL
111
+ );
112
+ --> statement-breakpoint
113
+ CREATE TABLE "oauth_refresh_token" (
114
+ "id" text PRIMARY KEY NOT NULL,
115
+ "token" text NOT NULL,
116
+ "client_id" text NOT NULL,
117
+ "session_id" text,
118
+ "user_id" text NOT NULL,
119
+ "reference_id" text,
120
+ "authorization_code_id" text,
121
+ "resources" text[],
122
+ "requested_user_info_claims" text[],
123
+ "expires_at" timestamp NOT NULL,
124
+ "created_at" timestamp NOT NULL,
125
+ "revoked" timestamp,
126
+ "rotated_at" timestamp,
127
+ "rotation_replay_response" text,
128
+ "rotation_replay_expires_at" timestamp,
129
+ "auth_time" timestamp,
130
+ "confirmation" jsonb,
131
+ "scopes" text[] NOT NULL,
132
+ CONSTRAINT "oauth_refresh_token_token_unique" UNIQUE("token")
133
+ );
134
+ --> statement-breakpoint
135
+ CREATE TABLE "oauth_resource" (
136
+ "id" text PRIMARY KEY NOT NULL,
137
+ "identifier" text NOT NULL,
138
+ "name" text NOT NULL,
139
+ "access_token_ttl" integer,
140
+ "refresh_token_ttl" integer,
141
+ "signing_algorithm" text,
142
+ "signing_key_id" text,
143
+ "allowed_scopes" text[],
144
+ "custom_claims" jsonb,
145
+ "dpop_bound_access_tokens_required" boolean DEFAULT false,
146
+ "disabled" boolean DEFAULT false,
147
+ "created_at" timestamp,
148
+ "updated_at" timestamp,
149
+ "policy_version" integer DEFAULT 1,
150
+ "metadata" jsonb,
151
+ CONSTRAINT "oauth_resource_identifier_unique" UNIQUE("identifier")
152
+ );
153
+ --> statement-breakpoint
154
+ CREATE TABLE "passkey" (
155
+ "id" text PRIMARY KEY NOT NULL,
156
+ "name" text,
157
+ "public_key" text NOT NULL,
158
+ "user_id" text NOT NULL,
159
+ "credential_id" text NOT NULL,
160
+ "counter" integer NOT NULL,
161
+ "device_type" text NOT NULL,
162
+ "backed_up" boolean NOT NULL,
163
+ "transports" text,
164
+ "created_at" timestamp,
165
+ "aaguid" text
166
+ );
167
+ --> statement-breakpoint
168
+ CREATE TABLE "session" (
169
+ "id" text PRIMARY KEY NOT NULL,
170
+ "expires_at" timestamp NOT NULL,
171
+ "token" text NOT NULL,
172
+ "created_at" timestamp DEFAULT now() NOT NULL,
173
+ "updated_at" timestamp NOT NULL,
174
+ "ip_address" text,
175
+ "user_agent" text,
176
+ "user_id" text NOT NULL,
177
+ "impersonated_by" text,
178
+ CONSTRAINT "session_token_unique" UNIQUE("token")
179
+ );
180
+ --> statement-breakpoint
181
+ CREATE TABLE "two_factor" (
182
+ "id" text PRIMARY KEY NOT NULL,
183
+ "secret" text NOT NULL,
184
+ "backup_codes" text NOT NULL,
185
+ "user_id" text NOT NULL,
186
+ "verified" boolean DEFAULT true,
187
+ "failed_verification_count" integer DEFAULT 0,
188
+ "locked_until" timestamp
189
+ );
190
+ --> statement-breakpoint
191
+ CREATE TABLE "user" (
192
+ "id" text PRIMARY KEY NOT NULL,
193
+ "name" text NOT NULL,
194
+ "email" text NOT NULL,
195
+ "email_verified" boolean DEFAULT false NOT NULL,
196
+ "image" text,
197
+ "created_at" timestamp DEFAULT now() NOT NULL,
198
+ "updated_at" timestamp DEFAULT now() NOT NULL,
199
+ "role" text,
200
+ "banned" boolean DEFAULT false,
201
+ "ban_reason" text,
202
+ "ban_expires" timestamp,
203
+ "two_factor_enabled" boolean DEFAULT false,
204
+ "tokens_revoked_at" timestamp,
205
+ "locale" text,
206
+ CONSTRAINT "user_email_unique" UNIQUE("email")
207
+ );
208
+ --> statement-breakpoint
209
+ CREATE TABLE "verification" (
210
+ "id" text PRIMARY KEY NOT NULL,
211
+ "identifier" text NOT NULL,
212
+ "value" text NOT NULL,
213
+ "expires_at" timestamp NOT NULL,
214
+ "created_at" timestamp DEFAULT now() NOT NULL,
215
+ "updated_at" timestamp DEFAULT now() NOT NULL
216
+ );
217
+ --> statement-breakpoint
218
+ CREATE TABLE "admin_action" (
219
+ "id" text PRIMARY KEY NOT NULL,
220
+ "actor_id" text,
221
+ "action" text NOT NULL,
222
+ "target_type" text,
223
+ "target_id" text,
224
+ "detail" jsonb,
225
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
226
+ );
227
+ --> statement-breakpoint
228
+ ALTER TABLE "admin_action" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
229
+ CREATE TABLE "purchase" (
230
+ "id" text PRIMARY KEY NOT NULL,
231
+ "user_id" text NOT NULL,
232
+ "provider" text NOT NULL,
233
+ "external_id" text NOT NULL,
234
+ "amount" text NOT NULL,
235
+ "currency" text DEFAULT 'usd' NOT NULL,
236
+ "status" text DEFAULT 'pending' NOT NULL,
237
+ "metadata" jsonb,
238
+ "paid_at" timestamp with time zone,
239
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
240
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
241
+ );
242
+ --> statement-breakpoint
243
+ ALTER TABLE "purchase" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
244
+ CREATE TABLE "system_config" (
245
+ "key" text PRIMARY KEY NOT NULL,
246
+ "value" jsonb NOT NULL,
247
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
248
+ );
249
+ --> statement-breakpoint
250
+ ALTER TABLE "system_config" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
251
+ ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
252
+ ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
253
+ ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
254
+ ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
255
+ ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_refresh_id_oauth_refresh_token_id_fk" FOREIGN KEY ("refresh_id") REFERENCES "public"."oauth_refresh_token"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
256
+ ALTER TABLE "oauth_client" ADD CONSTRAINT "oauth_client_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
257
+ ALTER TABLE "oauth_client_resource" ADD CONSTRAINT "oauth_client_resource_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
258
+ ALTER TABLE "oauth_client_resource" ADD CONSTRAINT "oauth_client_resource_resource_id_oauth_resource_identifier_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."oauth_resource"("identifier") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
259
+ ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
260
+ ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
261
+ ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
262
+ ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
263
+ ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
264
+ ALTER TABLE "passkey" ADD CONSTRAINT "passkey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
265
+ ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
266
+ ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
267
+ ALTER TABLE "admin_action" ADD CONSTRAINT "admin_action_actor_id_user_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
268
+ ALTER TABLE "purchase" ADD CONSTRAINT "purchase_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
269
+ CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
270
+ CREATE INDEX "oauthAccessToken_clientId_idx" ON "oauth_access_token" USING btree ("client_id");--> statement-breakpoint
271
+ CREATE INDEX "oauthAccessToken_sessionId_idx" ON "oauth_access_token" USING btree ("session_id");--> statement-breakpoint
272
+ CREATE INDEX "oauthAccessToken_userId_idx" ON "oauth_access_token" USING btree ("user_id");--> statement-breakpoint
273
+ CREATE INDEX "oauthAccessToken_authorizationCodeId_idx" ON "oauth_access_token" USING btree ("authorization_code_id");--> statement-breakpoint
274
+ CREATE INDEX "oauthAccessToken_refreshId_idx" ON "oauth_access_token" USING btree ("refresh_id");--> statement-breakpoint
275
+ CREATE INDEX "oauthClient_userId_idx" ON "oauth_client" USING btree ("user_id");--> statement-breakpoint
276
+ CREATE UNIQUE INDEX "oauthClientResource_clientId_resourceId_uidx" ON "oauth_client_resource" USING btree ("client_id","resource_id");--> statement-breakpoint
277
+ CREATE INDEX "oauthClientResource_clientId_idx" ON "oauth_client_resource" USING btree ("client_id");--> statement-breakpoint
278
+ CREATE INDEX "oauthClientResource_resourceId_idx" ON "oauth_client_resource" USING btree ("resource_id");--> statement-breakpoint
279
+ CREATE INDEX "oauthConsent_clientId_idx" ON "oauth_consent" USING btree ("client_id");--> statement-breakpoint
280
+ CREATE INDEX "oauthConsent_userId_idx" ON "oauth_consent" USING btree ("user_id");--> statement-breakpoint
281
+ CREATE INDEX "oauthRefreshToken_clientId_idx" ON "oauth_refresh_token" USING btree ("client_id");--> statement-breakpoint
282
+ CREATE INDEX "oauthRefreshToken_sessionId_idx" ON "oauth_refresh_token" USING btree ("session_id");--> statement-breakpoint
283
+ CREATE INDEX "oauthRefreshToken_userId_idx" ON "oauth_refresh_token" USING btree ("user_id");--> statement-breakpoint
284
+ CREATE INDEX "oauthRefreshToken_authorizationCodeId_idx" ON "oauth_refresh_token" USING btree ("authorization_code_id");--> statement-breakpoint
285
+ CREATE INDEX "passkey_userId_idx" ON "passkey" USING btree ("user_id");--> statement-breakpoint
286
+ CREATE INDEX "passkey_credentialID_idx" ON "passkey" USING btree ("credential_id");--> statement-breakpoint
287
+ CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
288
+ CREATE INDEX "twoFactor_secret_idx" ON "two_factor" USING btree ("secret");--> statement-breakpoint
289
+ CREATE INDEX "twoFactor_userId_idx" ON "two_factor" USING btree ("user_id");--> statement-breakpoint
290
+ CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");--> statement-breakpoint
291
+ CREATE INDEX "admin_action_created_idx" ON "admin_action" USING btree ("created_at" DESC NULLS LAST);--> statement-breakpoint
292
+ CREATE UNIQUE INDEX "purchase_provider_external_uq" ON "purchase" USING btree ("provider","external_id");--> statement-breakpoint
293
+ CREATE INDEX "purchase_userId_idx" ON "purchase" USING btree ("user_id");--> statement-breakpoint
294
+ -- is_admin(): service GUC bypass OR user.role in (admin, superadmin).
295
+ -- Must live after "user" exists — SQL functions validate relations at CREATE.
296
+ CREATE OR REPLACE FUNCTION app_private.is_admin()
297
+ RETURNS boolean
298
+ LANGUAGE sql
299
+ STABLE
300
+ SECURITY DEFINER
301
+ SET search_path = pg_catalog, public
302
+ AS $$
303
+ SELECT COALESCE(current_setting('app.service', true), '') = 'true'
304
+ OR EXISTS (
305
+ SELECT 1
306
+ FROM public."user" AS u
307
+ WHERE u.id = app_private.current_user_id()
308
+ AND u.role IN ('admin', 'superadmin')
309
+ );
310
+ $$;
311
+
312
+ REVOKE ALL ON FUNCTION app_private.is_admin() FROM PUBLIC;
313
+ GRANT EXECUTE ON FUNCTION app_private.is_admin() TO starter_app;
314
+ --> statement-breakpoint
315
+ CREATE POLICY "rls_policy" ON "admin_action" AS PERMISSIVE FOR ALL TO "starter_app" USING (app_private.is_admin()) WITH CHECK (app_private.is_admin());--> statement-breakpoint
316
+ CREATE POLICY "rls_user_read" ON "purchase" AS PERMISSIVE FOR SELECT TO "starter_app" USING (app_private.is_admin() OR user_id = app_private.current_user_id());--> statement-breakpoint
317
+ CREATE POLICY "rls_service_write" ON "purchase" AS PERMISSIVE FOR ALL TO "starter_app" USING (app_private.is_admin()) WITH CHECK (app_private.is_admin());--> statement-breakpoint
318
+ CREATE POLICY "rls_policy" ON "system_config" AS PERMISSIVE FOR ALL TO "starter_app" USING (app_private.is_admin()) WITH CHECK (app_private.is_admin());
319
+ --> statement-breakpoint
320
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO starter_app;