Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/skills/data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ reward amounts — goes in `<layer>/server/constants/<category>.ts`,
not mixed into a service file.

```
layers/auth/server/constants/defaults.ts SYSTEM_ORG, DEMO_ORG, SYSTEM_GRANTS, DEMO_ORG_GRANTS, SEED_USERS
layers/auth/server/constants/defaults.ts SYSTEM_ORG, DEMO_ORG, SYSTEM_GRANTS, SEED_USERS
layers/notification/server/constants/defaults.ts DEMO_NOTIFICATIONS
layers/support/server/constants/defaults.ts DEMO_TICKETS
layers/referral/server/constants/defaults.ts REFERRAL_REWARD
Expand Down Expand Up @@ -219,7 +219,7 @@ item, or a server endpoint **must** be paired with a permission review:
`TENANT_ABILITY_KEYS`, or `SELF_ABILITY_KEYS`).
2. Does any default grant set need updating?
`DEFAULT_PERSONAL_ORG_ABILITIES`, `DEFAULT_MEMBER_ABILITIES`,
`SYSTEM_GRANTS`, `DEMO_ORG_GRANTS` — each may need the new key.
`DEFAULT_ROLE_ABILITIES`, `SYSTEM_GRANTS` — each may need the new key.
3. Are existing live users in production missing the new ability?
Write an `update:` task that backfills it (e.g.
`update/user-abilities.ts`).
Expand Down
7 changes: 4 additions & 3 deletions .agents/skills/data/references/permission-aware-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ abilities live in three places that must stay synchronised:
| File | Role |
|---|---|
| `layers/auth/shared/permissions.ts` | The **catalog** — which ability keys exist and which org kind (system vs tenant) may hold them |
| `layers/auth/server/services/seed.ts` | The **default grants** — `SYSTEM_GRANTS`, `DEMO_ORG_GRANTS`, plus `DEFAULT_PERSONAL_ORG_ABILITIES` / `DEFAULT_MEMBER_ABILITIES` exported from `shared/permissions.ts` |
| `layers/auth/server/constants/defaults.ts` | `SYSTEM_GRANTS` (system-org only grants) |
| `layers/auth/shared/permissions.ts` | `DEFAULT_ROLE_ABILITIES` — demo/tenant tier grants (admin/member/guest); also `DEFAULT_PERSONAL_ORG_ABILITIES` / `DEFAULT_MEMBER_ABILITIES` |
| `layers/auth/server/tasks/create/admin.ts` (via `createSystemAdmin`) | The **production admin's grant set** — derived from `SYSTEM_GRANTS.admin` |

Drift between any of these three is a permission bug waiting to ship.
Expand Down Expand Up @@ -63,7 +64,7 @@ Walk the four grant sources:
| Grant | Who gets it | When to add the new key |
|---|---|---|
| `SYSTEM_GRANTS.admin` (in `services/seed.ts`) | Production system admin (via `create:admin`) and dev/demo admins | If this is a system-level power (`*:manage`, `*:audit`) |
| `DEMO_ORG_GRANTS.{admin, member, guest}` | Demo tenant org seed users | If the new key is a tenant ability — pick which tier needs it |
| `DEFAULT_ROLE_ABILITIES[DefaultRole.{ADMIN,MEMBER,GUEST}]` (in `shared/permissions.ts`) | Demo tenant org seed users and `roles:sync` materialized grants | If the new key is a tenant ability — pick which tier needs it |
| `DEFAULT_PERSONAL_ORG_ABILITIES` (in `shared/permissions.ts`) | Owner of a freshly-created personal org | If new tenants should have it by default |
| `DEFAULT_MEMBER_ABILITIES` (in `shared/permissions.ts`) | A user joining an org as a plain member (direct add or invitation accept) | If members (not just admins) should have it |

Expand Down Expand Up @@ -139,7 +140,7 @@ When deprecating a feature:

1. Remove all `<Can>` / `defineAuthorizedHandler` references to the
key first (the UI and routes stop using it)
2. Remove the key from `SYSTEM_GRANTS` / `DEMO_ORG_GRANTS` /
2. Remove the key from `SYSTEM_GRANTS` / `DEFAULT_ROLE_ABILITIES` /
`DEFAULT_*_ABILITIES` (the seed defaults stop granting it)
3. Write a `refactor/` task that removes the key from existing
memberships' `abilities` arrays (idempotent: read row, filter
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/data/references/task-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ service file.

```
layers/auth/server/constants/defaults.ts
SYSTEM_ORG, DEMO_ORG, SYSTEM_GRANTS, DEMO_ORG_GRANTS, SEED_USERS, SeedUserDef
SYSTEM_ORG, DEMO_ORG, SYSTEM_GRANTS, SEED_USERS, SeedUserDef

layers/notification/server/constants/defaults.ts
DEMO_NOTIFICATIONS, DemoNotificationDef
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/prep/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Pick an approach. Use `references/solution-design.md`.
"INSERT ..."`. Load the `data` skill for the full convention.
- **Permission impact**: list every ability key the plan touches.
Identify catalog updates (`layers/auth/shared/permissions.ts`),
grant-set updates (`SYSTEM_GRANTS`, `DEMO_ORG_GRANTS`,
grant-set updates (`SYSTEM_GRANTS`, `DEFAULT_ROLE_ABILITIES`,
`DEFAULT_*_ABILITIES`), and whether a live-env backfill (`update:`
task) is needed. See `data` skill's
`references/permission-aware-data.md`.
Expand Down Expand Up @@ -119,7 +119,7 @@ Before writing the plan file, verify:
writes
- Every permission-touching step names the catalog file
(`layers/auth/shared/permissions.ts`) and the grant set
(`SYSTEM_GRANTS` / `DEMO_ORG_GRANTS` / `DEFAULT_*_ABILITIES`) it
(`SYSTEM_GRANTS` / `DEFAULT_ROLE_ABILITIES` / `DEFAULT_*_ABILITIES`) it
updates

Use `references/handoff-to-cook.md` for the full checklist.
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/verify/references/permission-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ verifies everyone else.
| anonymous | none | public routes only |

The demo org seeds three membership tiers (`admin`, `member`, `guest`)
— see `DEMO_ORG_GRANTS` in `seed.ts`. Use those tiers when the change
— see `DEFAULT_ROLE_ABILITIES` in `shared/permissions.ts`. Use those tiers when the change
adds tenant-scoped abilities.

If a seed user is missing in the local DB:
Expand Down
10 changes: 6 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ NUXT_PUBLIC_BASE_DOMAIN=localhost:3002
NUXT_PUBLIC_SSL_ENABLED=false
NUXT_PUBLIC_DEMO_MODE=false

# Server-side gate for dev/demo-only auth routes and module stripping
# (eslint/test-utils/nuxt-security). Keep `false` outside the demo deploy.
NUXT_DEMO_MODE=false

# =============================================================================
# Auth secrets — generate with `pnpm auth:generate`
# =============================================================================
Expand Down Expand Up @@ -45,6 +41,12 @@ NUXT_GOOGLE_CLIENT_SECRET=
NUXT_GITHUB_CLIENT_ID=
NUXT_GITHUB_CLIENT_SECRET=

# =============================================================================
# Github release repo
# =============================================================================
NUXT_GITHUB_REPOSITORY="thecodeorigin/nuxt-template"
NUXT_GITHUB_TOKEN=""

# =============================================================================
# SePay (Vietnamese bank transfer payments)
# =============================================================================
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,20 @@ jobs:
# cron email links; OAuth redirects derive from the live request) and SSL=true.
# This step sets only the genuinely secret / preview-specific values:
# - NUXT_AUTH_SECRET — shared preview secret (stable so sessions survive re-deploys)
# - NUXT_DEMO_MODE / NUXT_PUBLIC_DEMO_MODE — keep the demo login enabled on previews.
# - NUXT_PUBLIC_BASE_DOMAIN — per-branch workers.dev host, derived from the deploy URL
# (used by non-request contexts like cron email links; OAuth
# redirects auto-derive from the live request).
# - NUXT_PUBLIC_DEMO_MODE — keep the demo login enabled on previews.
# - NUXT_PUBLIC_SSL_ENABLED — workers.dev is always HTTPS.
- name: Provision preview Worker secrets
env:
PREVIEW_NUXT_AUTH_SECRET: ${{ secrets.PREVIEW_NUXT_AUTH_SECRET }}
run: |
cat > "$RUNNER_TEMP/preview-secrets.json" <<EOF
{
"NUXT_AUTH_SECRET": "${PREVIEW_NUXT_AUTH_SECRET}",
"NUXT_DEMO_MODE": "true",
"NUXT_PUBLIC_BASE_DOMAIN": "${PREVIEW_HOST}",
"NUXT_PUBLIC_SSL_ENABLED": "true",
"NUXT_PUBLIC_DEMO_MODE": "true"
}
EOF
Expand Down
64 changes: 64 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
name: Build self-host bundle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build (Cloudflare Workers preset)
run: pnpm build
env:
NITRO_PRESET: cloudflare-module

- name: Pack self-host bundle
run: node scripts/make-deploy-bundle.mjs
env:
RELEASE_TAG: ${{ github.ref_name }}

- name: Pack tarball (manual deploy convenience)
run: tar -czf nuxt-template-${{ github.ref_name }}-cloudflare.tar.gz -C .output .

- name: Read SHA-256
id: hash
run: echo "sha256=$(cat bundle.sha256)" >> "$GITHUB_OUTPUT"

- name: Create release
uses: softprops/action-gh-release@v2
with:
files: |
bundle.json
nuxt-template-*-cloudflare.tar.gz
body: |
Self-host deployable bundle for `${{ github.ref_name }}`.

sha256:${{ steps.hash.outputs.sha256 }}

**For users self-hosting:** the application running on the upstream
instance will fetch `bundle.json` from this release automatically
when you click Deploy on the Self-hosting settings page. The SHA-256
above is verified before the worker is uploaded to your account.

**For manual deploy:** download `nuxt-template-${{ github.ref_name }}-cloudflare.tar.gz`,
extract, and run `wrangler deploy` against your account.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ test-results/
ux/

.wrangler

.tmp/*
89 changes: 87 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,89 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
"typescript.enablePromptUseWorkspaceTsdk": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in your IDE, but still auto fix them
"eslint.rules.customizations": [
{
"rule": "style/*",
"severity": "off",
"fixable": true
},
{
"rule": "format/*",
"severity": "off",
"fixable": true
},
{
"rule": "*-indent",
"severity": "off",
"fixable": true
},
{
"rule": "*-spacing",
"severity": "off",
"fixable": true
},
{
"rule": "*-spaces",
"severity": "off",
"fixable": true
},
{
"rule": "*-order",
"severity": "off",
"fixable": true
},
{
"rule": "*-dangle",
"severity": "off",
"fixable": true
},
{
"rule": "*-newline",
"severity": "off",
"fixable": true
},
{
"rule": "*quotes",
"severity": "off",
"fixable": true
},
{
"rule": "*semi",
"severity": "off",
"fixable": true
}
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
]
}
16 changes: 8 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ suggest `/onboard` before any feature work.
## Stack

- **Nuxt 4** with **layers** for feature isolation (`layers/auth/`,
`layers/todo/`). Each layer is auto-discovered and contributes its own
`layers/product/`, `layers/project/`, etc.). Each layer is auto-discovered and contributes its own
`app/`, `server/`, `shared/`. Cross-cutting infrastructure stays in the
project root.
- Vue 3, TypeScript
Expand Down Expand Up @@ -153,9 +153,9 @@ suggest `/onboard` before any feature work.
- `layers/auth/shared/permissions.ts` — catalog (`SYSTEM_ABILITY_KEYS`,
`TENANT_ABILITY_KEYS`, `DEFAULT_PERSONAL_ORG_ABILITIES`,
`DEFAULT_MEMBER_ABILITIES`)
- `layers/auth/server/services/seed.ts` — `SYSTEM_GRANTS`
(production system admin) and `DEMO_ORG_GRANTS` (demo tenant
tiers)
- `layers/auth/server/constants/defaults.ts` — `SYSTEM_GRANTS`
(production system admin); `layers/auth/shared/permissions.ts` —
`DEFAULT_ROLE_ABILITIES` (demo/new-tenant tiers, source of truth)
- Live users in deployed envs — need a permission-lifecycle task
to backfill the new ability so it reaches existing rows.
Sessions auto-refresh; no re-login needed.
Expand Down Expand Up @@ -296,8 +296,8 @@ ready. Full field reference and the current priority table are in
## API route conventions

- File: `<layer>/server/api/<resource>/<action>.{method}.ts` (e.g.
`layers/todo/server/api/todos/index.get.ts`,
`layers/todo/server/api/todos/[id].patch.ts`).
`layers/product/server/api/products/index.get.ts`,
`layers/product/server/api/products/[id].patch.ts`).
- Handler: use `defineEventHandler` (or `defineAuthenticatedHandler` for routes
that require a session, or `defineAuthorizedHandler` for ability-gated routes).
- Validate body with a Zod schema imported from `<layer>/shared/schemas/`.
Expand Down Expand Up @@ -475,8 +475,8 @@ suite). The rate-limiter driver is an in-process `lru-cache` (per Worker
isolate). Don't add a `$development.security` or `$test.security` block —
the goal is for dev/preview to behave just like production.

`NUXT_DEMO_MODE=true` on the Cloudflare preview/production env ungates
`/api/auth/demo-login` so the deployed app accepts the
`NUXT_PUBLIC_DEMO_MODE=true` on the Cloudflare preview/production env ungates
`/api/auth/demo/login` so the deployed app accepts the
"Sign in as Admin/User Agent" buttons. **Don't set it on a non-demo
deployment** — it's a deliberate backdoor for the demo project.

Expand Down
Loading
Loading