feat(api): /api/recipes/all akzeptiert sort=viewed + profile_id
VALID_SORTS um 'viewed' erweitert. parseProfileId-Helper analog zu /api/wishlist. Wert wird an listAllRecipesPaginated als 5. Param durchgereicht. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,19 @@ import {
|
|||||||
type AllRecipesSort
|
type AllRecipesSort
|
||||||
} from '$lib/server/recipes/search-local';
|
} from '$lib/server/recipes/search-local';
|
||||||
|
|
||||||
const VALID_SORTS = new Set<AllRecipesSort>(['name', 'rating', 'cooked', 'created']);
|
const VALID_SORTS = new Set<AllRecipesSort>([
|
||||||
|
'name',
|
||||||
|
'rating',
|
||||||
|
'cooked',
|
||||||
|
'created',
|
||||||
|
'viewed'
|
||||||
|
]);
|
||||||
|
|
||||||
|
function parseProfileId(raw: string | null): number | null {
|
||||||
|
if (!raw) return null;
|
||||||
|
const n = Number(raw);
|
||||||
|
return Number.isInteger(n) && n > 0 ? n : null;
|
||||||
|
}
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ url }) => {
|
export const GET: RequestHandler = async ({ url }) => {
|
||||||
const sortRaw = (url.searchParams.get('sort') ?? 'name') as AllRecipesSort;
|
const sortRaw = (url.searchParams.get('sort') ?? 'name') as AllRecipesSort;
|
||||||
@@ -17,6 +29,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
|||||||
// one round-trip so document height matches and scroll-restore lands.
|
// one round-trip so document height matches and scroll-restore lands.
|
||||||
const limit = Math.min(200, Math.max(1, Number(url.searchParams.get('limit') ?? 10)));
|
const limit = Math.min(200, Math.max(1, Number(url.searchParams.get('limit') ?? 10)));
|
||||||
const offset = Math.max(0, Number(url.searchParams.get('offset') ?? 0));
|
const offset = Math.max(0, Number(url.searchParams.get('offset') ?? 0));
|
||||||
const hits = listAllRecipesPaginated(getDb(), sortRaw, limit, offset);
|
const profileId = parseProfileId(url.searchParams.get('profile_id'));
|
||||||
|
const hits = listAllRecipesPaginated(getDb(), sortRaw, limit, offset, profileId);
|
||||||
return json({ sort: sortRaw, limit, offset, hits });
|
return json({ sort: sortRaw, limit, offset, hits });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -237,3 +237,34 @@ describe('POST /api/recipes/[id]/view', () => {
|
|||||||
).rejects.toMatchObject({ status: 404 });
|
).rejects.toMatchObject({ status: 404 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// GET /api/recipes/all — sort=viewed + profile_id
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import { GET as allGet } from '../../src/routes/api/recipes/all/+server';
|
||||||
|
|
||||||
|
describe('GET /api/recipes/all sort=viewed', () => {
|
||||||
|
it('passes profile_id through and returns viewed-order hits', async () => {
|
||||||
|
const db = openInMemoryForTest();
|
||||||
|
testDb.current = db;
|
||||||
|
const profile = createProfile(db, 'Test');
|
||||||
|
const a = seedRecipe(db, 'Apfel');
|
||||||
|
const b = seedRecipe(db, 'Birne');
|
||||||
|
recordView(db, profile.id, b);
|
||||||
|
await new Promise((r) => setTimeout(r, 1100));
|
||||||
|
recordView(db, profile.id, a);
|
||||||
|
|
||||||
|
const url = new URL(`http://localhost/api/recipes/all?sort=viewed&profile_id=${profile.id}&limit=10`);
|
||||||
|
const res = await allGet({ url } as never);
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = await res.json();
|
||||||
|
expect(body.sort).toBe('viewed');
|
||||||
|
expect(body.hits.map((h: { id: number }) => h.id)).toEqual([a, b]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('400 on invalid sort', async () => {
|
||||||
|
const url = new URL('http://localhost/api/recipes/all?sort=invalid');
|
||||||
|
await expect(allGet({ url } as never)).rejects.toMatchObject({ status: 400 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user