forked from EduCraft/curriculum-project-hub
feat(hub): drop redundant /admin/org/:slug path + release v0.0.35 (#11)
Silo hostname already carries tenancy. Admin SPA routes become /admin/..., legacy bookmarks redirect, login lands on /admin. Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
<div class="space-y-0.5">
|
||||
{#each childProjects as p (p.id)}
|
||||
<a
|
||||
href={`/admin/org/${slug}/projects/${p.id}`}
|
||||
href={`/admin/projects/${p.id}`}
|
||||
class="flex items-center gap-2.5 px-3 py-2.5 text-sm transition hover:bg-surface-100"
|
||||
>
|
||||
<span class="flex h-7 w-7 items-center justify-center border border-primary-200 bg-primary-50 text-primary-700">
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { MeResponse, OrgMembership } from './api';
|
||||
|
||||
/** Alpha Silo host prefix: <slug>.educraft[.dev].… */
|
||||
export function hostOrgSlug(hostname: string = typeof window !== 'undefined' ? window.location.hostname : ''): string | null {
|
||||
const host = hostname.toLowerCase();
|
||||
const m = host.match(/^([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.educraft(?:-dev)?\./);
|
||||
return m?.[1] ?? null;
|
||||
}
|
||||
|
||||
export function isOrgAdmin(org: OrgMembership | null | undefined): boolean {
|
||||
if (!org) return false;
|
||||
const role = String(org.role ?? '').toUpperCase();
|
||||
return role === 'OWNER' || role === 'ADMIN';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the tenancy for this browser session.
|
||||
* Prefers hostname slug (silo), then ?org=, then first admin membership, then first membership.
|
||||
*/
|
||||
export function resolveOrg(me: MeResponse | null | undefined, search: string = ''): OrgMembership | null {
|
||||
if (!me || me.organizations.length === 0) return null;
|
||||
const host = hostOrgSlug();
|
||||
if (host) {
|
||||
const byHost = me.organizations.find((o) => o.slug === host);
|
||||
if (byHost) return byHost;
|
||||
}
|
||||
const q = new URLSearchParams(search).get('org')?.trim();
|
||||
if (q) {
|
||||
const byQuery = me.organizations.find((o) => o.slug === q);
|
||||
if (byQuery) return byQuery;
|
||||
}
|
||||
const admin = me.organizations.find((o) => isOrgAdmin(o));
|
||||
return admin ?? me.organizations[0] ?? null;
|
||||
}
|
||||
|
||||
/** SPA paths no longer embed org slug (subdomain carries tenancy). */
|
||||
export function adminPath(rest: string = ''): string {
|
||||
const cleaned = rest.replace(/^\/+/, '');
|
||||
return cleaned === '' ? '/admin' : `/admin/${cleaned}`;
|
||||
}
|
||||
@@ -35,12 +35,9 @@ export async function loadSession(): Promise<void> {
|
||||
/**
|
||||
* Resolve org slug for org-scoped Feishu OAuth (`GET /auth/feishu/:orgSlug`).
|
||||
* Unscoped `/auth/feishu` is disabled unless allowLegacyFeishuOAuth is on.
|
||||
* Path no longer carries tenancy: prefer hostname silo slug, then ?org=.
|
||||
*/
|
||||
export function resolveLoginOrgSlug(): string | null {
|
||||
const path = window.location.pathname.split('/').filter(Boolean);
|
||||
if (path[0] === 'admin' && path[1] === 'org' && path[2]) {
|
||||
return decodeURIComponent(path[2]);
|
||||
}
|
||||
const q = new URLSearchParams(window.location.search).get('org');
|
||||
if (q && q.trim() !== '') return q.trim();
|
||||
|
||||
@@ -48,6 +45,12 @@ export function resolveLoginOrgSlug(): string | null {
|
||||
const host = window.location.hostname.toLowerCase();
|
||||
const m = host.match(/^([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.educraft(?:-dev)?\./);
|
||||
if (m?.[1]) return m[1];
|
||||
|
||||
// Legacy path while old bookmarks still land briefly before redirect.
|
||||
const path = window.location.pathname.split('/').filter(Boolean);
|
||||
if (path[0] === 'admin' && path[1] === 'org' && path[2]) {
|
||||
return decodeURIComponent(path[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user