Consolidate auth routes under /auth and rename /u to /user
Moves the login screen from /login to /auth, logout from /logout to /auth/logout (alongside the existing /auth/login and /auth/callback endpoints), and the public profile route from /u/[id] to /user/[id]. AUTH_REDIRECT_URI and AUTH_POST_LOGOUT_URI already point at /auth/callback and / respectively, so no Zitadel config change is needed for the auth move.
This commit is contained in:
@@ -46,7 +46,7 @@ src/routes/
|
|||||||
library/ /library Tu biblioteca ⬜
|
library/ /library Tu biblioteca ⬜
|
||||||
playlists/ /playlists Tus playlists ⬜
|
playlists/ /playlists Tus playlists ⬜
|
||||||
[id]/ /playlists/:id Detalle playlist ⬜
|
[id]/ /playlists/:id Detalle playlist ⬜
|
||||||
login logout auth/callback Auth (endpoints) ✅
|
auth auth/login auth/logout auth/callback Auth (página + endpoints) ✅
|
||||||
api/tracks/[id]/stream Proxy de stream ✅
|
api/tracks/[id]/stream Proxy de stream ✅
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -66,10 +66,11 @@ Página de entrada para no autenticados. Vender el producto y llevar a registro/
|
|||||||
|
|
||||||
## 2. Login / Auth ✅ (endpoints)
|
## 2. Login / Auth ✅ (endpoints)
|
||||||
|
|
||||||
No es una página visual propia: `/login` inicia el flujo OIDC de Zitadel y redirige.
|
`/auth` es la pantalla de login de Musify; sus botones llevan a `/auth/login`, que inicia
|
||||||
`?mode=register` arranca en registro; `?returnTo=` vuelve a la ruta pedida tras entrar.
|
el flujo OIDC de Zitadel y redirige. `?mode=register` arranca en registro; `?returnTo=`
|
||||||
`/logout` cierra sesión. `/auth/callback` intercambia el código y crea la cookie
|
vuelve a la ruta pedida tras entrar. `/auth/logout` cierra sesión. `/auth/callback`
|
||||||
cifrada `mf_session`. La UI de credenciales la sirve Zitadel, no Musify.
|
intercambia el código y crea la cookie cifrada `mf_session`. La UI de credenciales la
|
||||||
|
sirve Zitadel, no Musify.
|
||||||
|
|
||||||
## 3. Explorar / Buscar `/explore` ✅ (app)
|
## 3. Explorar / Buscar `/explore` ✅ (app)
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ Contenido de la página:
|
|||||||
- **Pipeline visible**: pasos Subir → Procesar → Publicar con su estado.
|
- **Pipeline visible**: pasos Subir → Procesar → Publicar con su estado.
|
||||||
- **Barra de progreso** de subida y estado de procesado.
|
- **Barra de progreso** de subida y estado de procesado.
|
||||||
- **Estados**: vacío, archivo seleccionado, subiendo, procesando, publicado, error.
|
- **Estados**: vacío, archivo seleccionado, subiendo, procesando, publicado, error.
|
||||||
- **Requiere sesión** (al cablear: redirigir a `/login?returnTo=/upload`).
|
- **Requiere sesión** (al cablear: redirigir a `/auth?returnTo=/upload`).
|
||||||
|
|
||||||
## 5. Tu biblioteca `/library` ⬜ (app)
|
## 5. Tu biblioteca `/library` ⬜ (app)
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ Listado de las playlists del usuario.
|
|||||||
- **Acciones del dueño**: editar (`PUT`), borrar (`DELETE`) la playlist, reordenar (futuro).
|
- **Acciones del dueño**: editar (`PUT`), borrar (`DELETE`) la playlist, reordenar (futuro).
|
||||||
- **Añadir tracks**: desde explorar/biblioteca (`POST /playlists/{id}/tracks/{trackId}`).
|
- **Añadir tracks**: desde explorar/biblioteca (`POST /playlists/{id}/tracks/{trackId}`).
|
||||||
|
|
||||||
## 8. Perfil de usuario `/users/[id]` ⬜ (futuro)
|
## 8. Perfil de usuario `/user/[id]` ⬜ (futuro)
|
||||||
|
|
||||||
Vista pública de un usuario: avatar, nombre, sus canciones y playlists públicas
|
Vista pública de un usuario: avatar, nombre, sus canciones y playlists públicas
|
||||||
(`GET /users/{id}`, `GET /tracks/users/{id}`, `GET /playlists/users/{id}`).
|
(`GET /users/{id}`, `GET /tracks/users/{id}`, `GET /playlists/users/{id}`).
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{#if name}
|
{#if name}
|
||||||
{#if ownerUserId}
|
{#if ownerUserId}
|
||||||
<a
|
<a
|
||||||
href="/u/{ownerUserId}"
|
href="/user/{ownerUserId}"
|
||||||
onclick={(e) => e.stopPropagation()}
|
onclick={(e) => e.stopPropagation()}
|
||||||
class="block max-w-full truncate hover:text-fg-2 hover:underline {klass}"
|
class="block max-w-full truncate hover:text-fg-2 hover:underline {klass}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
<GlassMenu class="absolute top-full right-0 z-(--z-menu) mt-2 overflow-hidden {WIDTH[width]}">
|
<GlassMenu class="absolute top-full right-0 z-(--z-menu) mt-2 overflow-hidden {WIDTH[width]}">
|
||||||
<MenuItem icon={User} label="Ver perfil" href="/u/{user.sub}" onclick={close} />
|
<MenuItem icon={User} label="Ver perfil" href="/user/{user.sub}" onclick={close} />
|
||||||
{@render extraItems?.({ close })}
|
{@render extraItems?.({ close })}
|
||||||
{#if accountUrl}
|
{#if accountUrl}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
label={themeMode.current === 'dark' ? 'Modo blanco' : 'Modo oscuro'}
|
label={themeMode.current === 'dark' ? 'Modo blanco' : 'Modo oscuro'}
|
||||||
onclick={() => themeMode.toggle()}
|
onclick={() => themeMode.toggle()}
|
||||||
/>
|
/>
|
||||||
<form method="POST" action="/logout" data-sveltekit-reload>
|
<form method="POST" action="/auth/logout" data-sveltekit-reload>
|
||||||
<MenuItem icon={LogOut} label="Cerrar sesión" type="submit" />
|
<MenuItem icon={LogOut} label="Cerrar sesión" type="submit" />
|
||||||
</form>
|
</form>
|
||||||
</GlassMenu>
|
</GlassMenu>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function createApiClient({ fetch, accessToken }: ApiClientOptions) {
|
|||||||
|
|
||||||
export function requireUser(locals: App.Locals, url: URL): SessionUser {
|
export function requireUser(locals: App.Locals, url: URL): SessionUser {
|
||||||
if (!locals.user)
|
if (!locals.user)
|
||||||
redirect(302, `/login?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
||||||
return locals.user;
|
return locals.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export function optionalUser(
|
|||||||
): SessionUser | null {
|
): SessionUser | null {
|
||||||
if (locals.user) return locals.user;
|
if (locals.user) return locals.user;
|
||||||
if (!allowAnonymousListening)
|
if (!allowAnonymousListening)
|
||||||
redirect(302, `/login?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
if (!topResult || topResult.kind === 'track') return undefined;
|
if (!topResult || topResult.kind === 'track') return undefined;
|
||||||
if (topResult.kind === 'album') return `/albums/${topResult.album.id}`;
|
if (topResult.kind === 'album') return `/albums/${topResult.album.id}`;
|
||||||
if (topResult.kind === 'playlist') return `/playlists/${topResult.playlist.id}`;
|
if (topResult.kind === 'playlist') return `/playlists/${topResult.playlist.id}`;
|
||||||
return `/u/${topResult.user.id}`;
|
return `/user/${topResult.user.id}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
function togglePlay(index: number) {
|
function togglePlay(index: number) {
|
||||||
@@ -400,7 +400,7 @@
|
|||||||
<ul class="flex flex-col gap-1">
|
<ul class="flex flex-col gap-1">
|
||||||
{#each capped(sfilter, users) as u (u.id)}
|
{#each capped(sfilter, users) as u (u.id)}
|
||||||
<li>
|
<li>
|
||||||
<ListRow title={u.name} href="/u/{u.id}" size="lg">
|
<ListRow title={u.name} href="/user/{u.id}" size="lg">
|
||||||
{#snippet art()}
|
{#snippet art()}
|
||||||
<Avatar name={u.name} src={u.profilePictureUrl} size="md" />
|
<Avatar name={u.name} src={u.profilePictureUrl} size="md" />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createApiClient } from '$lib/server/api';
|
|||||||
import { authConfig } from '$lib/server/config';
|
import { authConfig } from '$lib/server/config';
|
||||||
import { LIKED_TRACKS_PAGE_SIZE } from '$lib/config';
|
import { LIKED_TRACKS_PAGE_SIZE } from '$lib/config';
|
||||||
|
|
||||||
const PUBLIC_PATHS = new Set(['/login']);
|
const PUBLIC_PATHS = new Set(['/auth']);
|
||||||
const SIDEBAR_PLAYLISTS_LIMIT = 8;
|
const SIDEBAR_PLAYLISTS_LIMIT = 8;
|
||||||
|
|
||||||
async function fetchSidebarPlaylists(
|
async function fetchSidebarPlaylists(
|
||||||
@@ -43,7 +43,7 @@ export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
|
|||||||
const allowAnonymousListening = await getAllowAnonymousListening(fetch);
|
const allowAnonymousListening = await getAllowAnonymousListening(fetch);
|
||||||
|
|
||||||
if (!locals.user && !allowAnonymousListening && !PUBLIC_PATHS.has(url.pathname)) {
|
if (!locals.user && !allowAnonymousListening && !PUBLIC_PATHS.has(url.pathname)) {
|
||||||
redirect(302, `/login?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountUrl = authConfig.issuer
|
const accountUrl = authConfig.issuer
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
if (data.likedTracks?.length) liked.hydrate(data.likedTracks);
|
if (data.likedTracks?.length) liked.hydrate(data.likedTracks);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAuthPage = $derived(page.url.pathname === '/login');
|
const isAuthPage = $derived(page.url.pathname === '/auth');
|
||||||
const hasTrack = $derived(player.currentId !== null);
|
const hasTrack = $derived(player.currentId !== null);
|
||||||
|
|
||||||
const initialThemeCss = `:root{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'dark'))}}:root[data-theme='light']{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'light'))}}`;
|
const initialThemeCss = `:root{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'dark'))}}:root[data-theme='light']{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'light'))}}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user