Public Access
feat(library): add game links, auditor queue, audit log and filter
LinkGameDialog (user request or auditor link), EditionsCard on the details page, LinkQueuePage and AuditLogPage for auditors with paging, hideDuplicates filter and editions badge in the library, audit menu entries, de/en texts.
This commit is contained in:
1 parent
80259cb0e9
commit
f71e8f7aff
21 files changed
+992
-11
No files matched your search
@@ -56,9 +56,10 @@ export const dismissFinishedHint = (appid, dismissed) =>
|
||||
body: JSON.stringify({dismissed}),
|
||||
});
|
||||
|
||||
export const libraryQuery = ({page, size, sort, status, q, maxMinutes, hidden}) => {
|
||||
export const libraryQuery = ({page, size, sort, status, q, maxMinutes, hidden, hideDuplicates}) => {
|
||||
const params = new URLSearchParams({page: String(page), size: String(size), sort});
|
||||
if (hidden) params.set('hidden', 'true');
|
||||
if (hideDuplicates) params.set('hideDuplicates', 'true');
|
||||
if (status) params.set('status', status);
|
||||
if (q?.trim()) params.set('q', q.trim());
|
||||
if (maxMinutes) params.set('maxMinutes', String(maxMinutes));
|
||||
|
||||
@@ -48,3 +48,11 @@ describe('backlogShare', () => {
|
||||
expect(backlogShare(null)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('libraryQuery hideDuplicates', () => {
|
||||
it('libraryQuery_SetsHideDuplicatesOnlyWhenAsked', () => {
|
||||
const base = {page: 0, size: 20, sort: 'appid,asc', status: null, q: ''};
|
||||
expect(libraryQuery({...base, hideDuplicates: true})).toBe('page=0&size=20&sort=appid%2Casc&hideDuplicates=true');
|
||||
expect(libraryQuery({...base, hideDuplicates: false})).not.toContain('hideDuplicates');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import {apiFetch, request} from './api.js';
|
||||
import {libraryQuery} from './backlog.js';
|
||||
|
||||
const JSON_HEADERS = {'Content-Type': 'application/json'};
|
||||
const base = () => `${import.meta.env.VITE_CORE_API_URL}/api/library`;
|
||||
|
||||
/**
|
||||
* GET: 200 `{content: [{a, b, igdb, requests, createdAt}], totalPages}` (a and b are `{appid, name, imageUrl}`), 403
|
||||
* below AUDITOR.
|
||||
*/
|
||||
export const fetchLinkQueue = ({page, size}) => apiFetch(`${base()}/link-queue?page=${page}&size=${size}`);
|
||||
|
||||
/**
|
||||
* POST: 204, 400 SAME_GAME, 403 below AUDITOR, 404 GAME_NOT_FOUND, 409 ALREADY_LINKED.
|
||||
*/
|
||||
export const linkGames = (appid, otherAppid) =>
|
||||
apiFetch(`${base()}/games/${appid}/link`, {method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({otherAppid})});
|
||||
|
||||
/**
|
||||
* DELETE: 204, 404 NOT_LINKED.
|
||||
*/
|
||||
export const unlinkGame = (appid) => apiFetch(`${base()}/games/${appid}/link`, {method: 'DELETE'});
|
||||
|
||||
/**
|
||||
* DELETE: 204, 404 PAIR_NOT_OPEN.
|
||||
*/
|
||||
export const dismissPair = (appid, otherAppid) => apiFetch(`${base()}/link-queue/${appid}/${otherAppid}`, {method: 'DELETE'});
|
||||
|
||||
/**
|
||||
* POST: 204, 400 SAME_GAME, 404 GAME_NOT_FOUND, 409 ALREADY_LINKED or PAIR_DISMISSED, 429 TOO_MANY_REQUESTS.
|
||||
*/
|
||||
export const requestLink = (appid, otherAppid) =>
|
||||
apiFetch(`${base()}/games/${appid}/link-requests`,
|
||||
{method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({otherAppid})});
|
||||
|
||||
/**
|
||||
* The user's own games that match the query, as a library page `{content: [...]}`.
|
||||
*/
|
||||
export const searchOwnGames = (q, signal) =>
|
||||
request(`${base()}/games?${libraryQuery({page: 0, size: 20, sort: 'playtimeMinutes,desc', q})}`, {signal});
|
||||
|
||||
/**
|
||||
* GET: 200 `{content: [AuditEntry], totalPages}`, 403 below AUDITOR. An empty action means all.
|
||||
*/
|
||||
export const fetchAuditLog = ({action, page, size}) => {
|
||||
const params = new URLSearchParams({page: String(page), size: String(size)});
|
||||
if (action) params.set('action', action);
|
||||
return apiFetch(`${base()}/audit-log?${params}`);
|
||||
};
|
||||
Reference in new issue
Block a user