hyper-tuner-cloud/src/hooks/useDb.ts

227 lines
5.9 KiB
TypeScript

import * as Sentry from '@sentry/browser';
import {
client,
formatError,
ClientResponseError,
API_URL,
} from '../pocketbase';
import { databaseGenericError } from '../pages/auth/notifications';
import {
Collections,
IniFilesResponse,
TunesRecord,
TunesResponse,
} from '../@types/pocketbase-types';
type Partial<T> = {
[A in keyof T]?: T[A];
};
export type TunesRecordPartial = Partial<TunesRecord>;
type TunesResponseList = {
items: TunesResponse[];
totalItems: number;
}
const tunesCollection = client.collection(Collections.Tunes);
const customEndpoint = `${API_URL}/api/custom`;
const headers = (token: string) => ({
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
});
const useDb = () => {
const updateTune = async (id: string, data: TunesRecordPartial): Promise<void> => {
try {
await tunesCollection.update(id, data);
return Promise.resolve();
} catch (error) {
Sentry.captureException(error);
databaseGenericError(new Error(formatError(error)));
return Promise.reject(error);
}
};
const createTune = async (data: TunesRecord): Promise<TunesResponse> => {
try {
const record = await tunesCollection.create<TunesResponse>(data);
return Promise.resolve(record);
} catch (error) {
Sentry.captureException(error);
databaseGenericError(new Error(formatError(error)));
return Promise.reject(error);
}
};
const getTune = async (tuneId: string): Promise<TunesResponse | null> => {
const response = await fetch(`${customEndpoint}/tunes/byTuneId/${tuneId}`);
if (response.ok) {
return response.json();
}
if (response.status === 404) {
return Promise.resolve(null);
}
Sentry.captureException(response);
databaseGenericError(new Error(response.statusText));
return Promise.reject(response.status);
};
const getIni = async (signature: string): Promise<IniFilesResponse | null> => {
const response = await fetch(`${customEndpoint}/iniFiles/bySignature/${signature}`);
if (response.ok) {
return response.json();
}
if (response.status === 404) {
return Promise.resolve(null);
}
Sentry.captureException(response);
databaseGenericError(new Error(response.statusText));
return Promise.reject(response.status);
};
const searchTunes = async (search: string, page: number, perPage: number): Promise<TunesResponseList> => {
const phrases = search.length > 0 ? search.replace(/ +(?= )/g, '').split(' ') : [];
const filter = phrases
.filter((phrase) => phrase.length > 1)
.map((phrase) => `textSearch ~ "${phrase}" || author.username ~ "${phrase}"`)
.join(' && ');
try {
const list = await tunesCollection.getList<TunesResponse>(page, perPage, {
sort: '-stars,-updated',
filter,
expand: 'author',
});
return Promise.resolve({
items: list.items,
totalItems: list.totalItems,
});
} catch (error) {
if ((error as ClientResponseError).isAbort) {
return Promise.reject(new Error('Cancelled'));
}
Sentry.captureException(error);
databaseGenericError(new Error(formatError(error)));
return Promise.reject(error);
}
};
const getUserTunes = async (userId: string, page: number, perPage: number): Promise<TunesResponseList> => {
try {
const list = await tunesCollection.getList<TunesResponse>(page, perPage, {
sort: '-updated',
filter: `author = "${userId}"`,
expand: 'author',
});
return Promise.resolve({
items: list.items,
totalItems: list.totalItems,
});
} catch (error) {
if ((error as ClientResponseError).isAbort) {
return Promise.reject(new Error('Cancelled'));
}
Sentry.captureException(error);
databaseGenericError(new Error(formatError(error)));
return Promise.reject(error);
}
};
const autocomplete = async (attribute: string, search: string): Promise<TunesResponse[]> => {
try {
const items = await tunesCollection.getFullList<TunesResponse>(10, {
filter: `${attribute} ~ "${search}"`,
});
return Promise.resolve(items);
} catch (error) {
if ((error as ClientResponseError).isAbort) {
return Promise.reject(new Error('Cancelled'));
}
Sentry.captureException(error);
databaseGenericError(new Error(formatError(error)));
return Promise.reject(error);
}
};
const toggleStar = async (currentUserToken: string, tune: string): Promise<{ stars: number, isStarred: boolean }> => {
const response = await fetch(`${customEndpoint}/stargazers/toggleStar`, {
method: 'POST',
headers: headers(currentUserToken),
body: JSON.stringify({ tune }),
});
if (response.ok) {
const { stars, isStarred } = await response.json();
return Promise.resolve({ stars, isStarred });
}
if (response.status === 404) {
return Promise.resolve({ stars: 0, isStarred: false });
}
Sentry.captureException(response);
databaseGenericError(new Error(response.statusText));
return Promise.reject(response.status);
};
const isStarredByMe = async (currentUserToken: string, tune: string): Promise<boolean> => {
const response = await fetch(`${customEndpoint}/stargazers/starredByMe/${tune}`, {
headers: headers(currentUserToken),
});
if (response.ok) {
const { isStarred } = await response.json();
return Promise.resolve(isStarred);
}
if (response.status === 404) {
return Promise.resolve(false);
}
Sentry.captureException(response);
databaseGenericError(new Error(response.statusText));
return Promise.reject(response.status);
};
return {
updateTune,
createTune,
getTune,
getIni,
searchTunes,
getUserTunes,
autocomplete,
toggleStar,
isStarredByMe,
};
};
export default useDb;