skillhub/web
青柠 ae23d1a051 feat(namespace): auto-provision a personal namespace on registration
Self-hosted deployments want every new account to have somewhere of its
own to publish, without asking an administrator for a namespace first and
without pushing drafts into `global`.

Add an operator-controlled policy, off by default so upgrading changes no
behaviour. When enabled, an account that becomes usable gets a namespace
it owns. "Private" here means a team namespace whose only member is that
account: namespaces have no visibility flag, and skill visibility stays a
property of each skill.

Trigger points. UserActivatedEvent is published wherever an account first
becomes usable:

- LocalAuthService.register
- IdentityBindingService.bindOrCreate, for ACTIVE first logins
- AdminUserAppService.updateUserStatus, on a transition into ACTIVE

The third matters for deployments that gate access behind approval: those
accounts are created PENDING at the first OAuth attempt and only become
usable when an administrator approves them.

Why an AFTER_COMMIT listener rather than a call alongside
GlobalNamespaceMembershipService.ensureMember. Both namespace.created_by
and namespace_member.user_id reference user_account(id), which rules out
each obvious alternative:

- Joining the registration transaction lets a slug clash roll the
  registration back, so a namespace failure costs the user their account
  — or, on OAuth, their login.
- Suspending it with REQUIRES_NEW leaves the new transaction unable to see
  the uncommitted user_account row, so the foreign key check blocks on the
  outer transaction's row lock and the two wait on each other.

Provisioning after commit avoids both. The listener is deliberately not
@Async, so the namespace exists by the time the user's next request
arrives, and it swallows failures.

Naming. Two templates over ${username}, ${email_prefix} and ${user_id};
unknown placeholders are left in place so a typo is visible rather than
silently dropped. ${username} falls back to the email local part and then
to the user id. Slugs go through the existing slug rules, which is why the
console renders a live preview: underscores are not legal in a slug, so
`${username}_space` yields `alice-space`. A taken or reserved candidate
gets a numeric suffix, so `admin` becomes `admin-2`. Owning any non-global
namespace already skips provisioning, which keeps re-enabling an account
from handing out a second one.

The templates are not exposed in application.yml: they contain ${...},
which Spring would resolve as property references, and Boot 3.2 predates
placeholder escaping. Only the enable flag lives there; templates are set
in the console and default from PersonalNamespaceProvisioningProperties.

Updating the policy writes an audit entry with the before and after.
2026-08-13 18:17:25 +08:00
..
docker-entrypoint.d feat(web): support configurable base-path deployment 2026-08-05 12:50:26 +08:00
e2e fix(publish): delete review tasks of any status when replacing a version (#601) 2026-07-28 13:57:35 +08:00
public feat(auth): add GitLab OAuth2 provider support (#264) 2026-04-17 19:56:32 +08:00
src feat(namespace): auto-provision a personal namespace on registration 2026-08-13 18:17:25 +08:00
.browserslistrc fix(web): restore compatibility with Chromium 83 (Debian 10) 2026-05-09 13:55:50 +08:00
.dockerignore feat(ops): improve Dockerfile caching, add dockerignore, docker profile, Makefile help 2026-03-12 17:50:34 +08:00
.eslintrc.cjs refactor: address code review findings from 2026-03-22 2026-03-23 13:48:23 +08:00
base-path-config.test.ts feat(web): support configurable base-path deployment 2026-08-05 12:50:26 +08:00
base-path-config.ts feat(web): support configurable base-path deployment 2026-08-05 12:50:26 +08:00
components.json feat(web): implement login page, dashboard, and API token management 2026-03-12 00:13:50 +08:00
Dockerfile feat(web): support configurable base-path deployment 2026-08-05 12:50:26 +08:00
index.html fix(web): stop React 19 portal removeChild/insertBefore crashes (#624) 2026-08-04 13:59:37 +08:00
LANDING_PAGE_REDESIGN.md feat(web): redesign homepage with modern landing page 2026-03-12 22:27:58 +08:00
nginx.conf.template feat(web): support configurable base-path deployment 2026-08-05 12:50:26 +08:00
package.json fix(web): stop React 19 portal removeChild/insertBefore crashes (#624) 2026-08-04 13:59:37 +08:00
playwright.config.ts feat(promotion): improve promotion review dashboard 2026-06-24 09:28:43 +08:00
playwright.smoke.config.ts test(e2e,ci): migrate web e2e to real API and add PR e2e workflow (#198) 2026-04-01 14:41:23 +08:00
pnpm-lock.yaml fix(web): stop React 19 portal removeChild/insertBefore crashes (#624) 2026-08-04 13:59:37 +08:00
postcss.config.js feat(web): implement login page, dashboard, and API token management 2026-03-12 00:13:50 +08:00
PREVIEW.md docs: add landing page preview guide 2026-03-12 22:28:31 +08:00
runtime-config.js.template Add extensible auth compatibility layer for private SSO 2026-03-13 18:00:22 +08:00
tailwind.config.ts feat: support Unicode characters in skill slugs (#196) 2026-03-31 10:17:34 +08:00
TODO.md fix(web): fix i18n language switching and add translations 2026-03-13 13:42:57 +08:00
tsconfig.json fix(web): isolate ambient types (ISSUE-92) 2026-07-29 10:44:49 +08:00
vite.config.ts feat(web): support configurable base-path deployment 2026-08-05 12:50:26 +08:00