fix(activities): preserve viewer's sort_position on single-row fetches
Toggling "gjort" (and heart, and bookmark, and edit, and GET /:id) silently reset the row's effective sort_position to -created_at, wiping any custom drag-sort the viewer had applied to that row. The list endpoint joins user_activity_sort to get the per-viewer position; single-row endpoints were doing plain `SELECT * FROM activities WHERE id = ?` and serialize() was falling back to -created_at when sort_position was missing from the row. User-visible effect on the private list (which often has custom ordering since it's the user's todo list): toggling a checkbox made that row jump back to its created_at slot. Fix: fetchRowForViewer(id, viewerId) helper that does the same LEFT JOIN as the list query. Routed through every single-row return path — GET /:id, POST /, PATCH /:id, POST/DELETE /:id/heart, POST/DELETE /:id/bookmark, POST/DELETE /:id/done. Regression test covers heart + done + GET-by-id all preserving a custom sort_position written via PATCH /:id/sort.
This commit is contained in:
parent
bbb5ad2bdd
commit
fb193b4914
2 changed files with 69 additions and 5 deletions
|
|
@ -333,6 +333,42 @@ describe('per-user sort order', () => {
|
|||
expect(finalIds[0]).toBe(fresh.id);
|
||||
});
|
||||
|
||||
ttest('single-row endpoints preserve the viewer\'s custom sort_position', async () => {
|
||||
// Regression: heart / bookmark / done toggles (and PATCH/GET-by-id) used
|
||||
// to do plain `SELECT * FROM activities` without the user_activity_sort
|
||||
// LEFT JOIN, so serialize() silently fell back to -created_at and
|
||||
// overwrote any custom position. Now they go through fetchRowForViewer.
|
||||
const user = await signupAndGetCookie(ctx, 'sort-toggle@test.invalid');
|
||||
const a1 = await createActivity(ctx, user.cookie, { visibility: 'public', title: 'one', tags: [] });
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
const a2 = await createActivity(ctx, user.cookie, { visibility: 'public', title: 'two', tags: [] });
|
||||
|
||||
// Drag a1 below a2 (more positive sort_position → later in the ASC sort).
|
||||
const customPos = -a2.created_at + 100;
|
||||
await reqJson(ctx, 'PATCH', `/api/activities/${a1.id}/sort`, {
|
||||
cookie: user.cookie, body: { position: customPos },
|
||||
});
|
||||
|
||||
// Heart toggle should return a1 with the SAME custom sort_position,
|
||||
// not the default -created_at.
|
||||
const hearted = await reqJson<Activity>(ctx, 'POST', `/api/activities/${a1.id}/heart`, {
|
||||
cookie: user.cookie,
|
||||
});
|
||||
expect(hearted.sort_position).toBe(customPos);
|
||||
|
||||
// Same for done.
|
||||
const doneRes = await reqJson<Activity>(ctx, 'POST', `/api/activities/${a1.id}/done`, {
|
||||
cookie: user.cookie,
|
||||
});
|
||||
expect(doneRes.sort_position).toBe(customPos);
|
||||
|
||||
// And for GET /:id.
|
||||
const single = await reqJson<Activity>(ctx, 'GET', `/api/activities/${a1.id}`, {
|
||||
cookie: user.cookie,
|
||||
});
|
||||
expect(single.sort_position).toBe(customPos);
|
||||
});
|
||||
|
||||
test('PATCH /sort requires auth', async () => {
|
||||
const res = await req(ctx, 'PATCH', '/api/activities/whatever/sort', { body: { position: 1 } });
|
||||
expect(res.status).toBe(401);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue