WebStudio/src/api.ts
2021-09-08 00:08:28 +01:00

387 lines
9.1 KiB
TypeScript

import qs from "qs";
import { getUserError, setCurrentUser } from "./session/state";
import store from "./store";
export const MYRADIO_NON_API_BASE = process.env.REACT_APP_MYRADIO_NONAPI_BASE!;
export const MYRADIO_BASE_URL = process.env.REACT_APP_MYRADIO_BASE!;
export const BROADCAST_API_BASE_URL = process.env.REACT_APP_BROADCAST_API_BASE!;
export class ApiException extends Error {}
export async function apiRequest(
url: string,
method: "GET" | "POST" | "PUT",
params: any,
need_auth: boolean = true
): Promise<Response> {
var req: Promise<Response> | null = null;
if (method === "GET") {
req = fetch(url + qs.stringify(params, { addQueryPrefix: true }), {
credentials: need_auth ? "include" : "omit",
});
} else {
const body = JSON.stringify(params);
console.log(body);
req = fetch(url, {
method,
body,
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
credentials: need_auth ? "include" : "omit",
});
}
return await req;
}
export async function myradioApiRequest(
endpoint: string,
method: "GET" | "POST" | "PUT",
params: any
): Promise<any> {
const res = await apiRequest(MYRADIO_BASE_URL + endpoint, method, params);
const json = await res.json();
if (json.status === "OK") {
return json.payload;
} else {
if (res.status === 401) {
// We've logged out! Oh no!
store.dispatch(setCurrentUser({ user: null, canBroadcast: false }));
store.dispatch(getUserError("User is no longer logged in."));
} else {
console.error(json.payload);
throw new ApiException(json.payload);
}
}
}
export async function bapsicleApiRequest(
endpoint: string,
method: "GET" | "POST" | "PUT",
params: any
): Promise<any> {
let server = store.getState().bapsSession.currentServer;
if (!server) {
throw new Error("Trying to call BAPSicle server without connection.");
}
const url = `${server.ui_protocol}://${server.hostname}:${server.ui_port}${endpoint}`;
const res = await apiRequest(url, method, params);
const json = await res.json();
return json;
}
export async function broadcastApiRequest<TRes = any>(
endpoint: string,
method: "GET" | "POST" | "PUT",
params: any
): Promise<TRes> {
const res = await apiRequest(
BROADCAST_API_BASE_URL + endpoint,
method,
params,
false
);
const json = await res.json();
if (json.status === "OK") {
return json.payload;
} else {
console.error(json.reason);
throw new ApiException(json.reason);
}
}
interface Album {
title: string;
recordid: number;
artist: string;
cdid: number | null;
date_added: string;
date_released: string;
// TODO
}
export interface TimeslotItemBase {
timeslotitemid: string;
channel: number;
weight: number;
title: string;
length: string;
clean: boolean;
cue: number;
}
export interface TimeslotItemCentral {
type: "central";
trackid: number;
artist: string;
intro: number;
outro: number;
clean: boolean;
digitised: boolean;
album: Album;
}
export interface AuxItem {
type: "aux";
summary: string | number;
title: string | number;
managedid: number;
length: string;
expirydate: boolean | string;
expired: boolean;
auxid: string;
}
interface TimeslotItemAux extends AuxItem {
type: "aux";
}
export type TimeslotItem = TimeslotItemBase &
(TimeslotItemCentral | TimeslotItemAux);
export type Showplan = TimeslotItem[][];
export function getShowplan(showId: number): Promise<Showplan> {
return myradioApiRequest(
`/timeslot/${showId.toString(10)}/showplan`,
"GET",
{}
).then((res) => {
return Object.keys(res).map((x) => res[x]);
});
}
function wrapPromise<T, TArgs>(factory: (...args: TArgs[]) => Promise<T>) {
let status = "pending";
let result: T;
let suspender: Promise<void>;
return {
read(...args: TArgs[]) {
if (!(suspender instanceof Promise)) {
suspender = factory(...args).then(
(r) => {
status = "success";
result = r;
},
(e) => {
status = "error";
result = e;
}
);
}
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
} else {
throw new Error("Can't happen.");
}
},
};
}
export interface Track {
type: "central";
title: string;
artist: string;
album: Album;
trackid: number;
length: string;
intro: number;
outro: number;
clean: boolean;
digitised: boolean;
}
export const showPlanResource = wrapPromise<Showplan, number>(getShowplan);
export function searchForTracks(
artist: string,
title: string
): Promise<Array<Track>> {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
return bapsicleApiRequest("/library/search/track", "GET", {
artist,
title,
});
}
return myradioApiRequest("/track/search", "GET", {
artist,
title,
limit: 100,
digitised: true,
});
}
export function getTimeslots(): Promise<Array<Timeslot>> {
return bapsicleApiRequest("/plan/list", "GET", {});
}
export interface NipswebPlaylist {
type: "userPlaylist";
title: string;
managedid: string;
folder: string;
}
export interface ManagedPlaylist {
type: "managedPlaylist";
title: string;
playlistid: string;
folder: string;
}
export function getUserPlaylists(): Promise<Array<NipswebPlaylist>> {
return myradioApiRequest(
"/nipswebUserPlaylist/allmanageduserplaylists",
"GET",
{}
);
}
// Gets the list of managed music playlists.
export function getManagedPlaylists(): Promise<Array<ManagedPlaylist>> {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
return bapsicleApiRequest("/library/playlists/music", "GET", {});
}
return myradioApiRequest("/playlist/allitonesplaylists", "GET", {});
}
// Gets the list of managed aux playlists (sfx, beds etc.)
export function getAuxPlaylists(): Promise<Array<NipswebPlaylist>> {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
return bapsicleApiRequest("/library/playlists/aux", "GET", {});
}
return myradioApiRequest("/nipswebPlaylist/allmanagedplaylists", "GET", {});
}
// Loads the playlist items for a certain aux playlist
export function loadAuxLibrary(libraryId: string): Promise<AuxItem[]> {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
return bapsicleApiRequest("/library/playlist/aux/" + libraryId, "GET", {});
}
return apiRequest(MYRADIO_NON_API_BASE + "/NIPSWeb/load_aux_lib", "GET", {
libraryid: libraryId,
}).then((res) => res.json());
}
export function loadPlaylistLibrary(libraryId: string): Promise<Track[]> {
if (process.env.REACT_APP_BAPSICLE_INTERFACE) {
return bapsicleApiRequest(
"/library/playlist/music/" + libraryId,
"GET",
{}
);
}
return myradioApiRequest("/playlist/" + libraryId + "/tracks", "GET", {});
}
export function setTimeslotItemCue(
timeslotItemId: string,
secs: number
): Promise<null> {
return myradioApiRequest("/timeslotItem/" + timeslotItemId + "/cue", "PUT", {
start_time: secs,
});
}
export function setTrackIntro(trackId: number, secs: number): Promise<null> {
return myradioApiRequest("/track/" + trackId + "/intro", "PUT", {
duration: secs,
});
}
export function setTrackOutro(trackId: number, secs: number): Promise<null> {
return myradioApiRequest("/track/" + trackId + "/outro", "PUT", {
duration: secs,
});
}
export type UpdateOp =
| {
op: "MoveItem";
timeslotitemid: string;
oldchannel: number;
oldweight: number;
channel: number;
weight: number;
}
| {
op: "AddItem";
channel: number;
weight: number;
id: string;
}
| {
op: "RemoveItem";
timeslotitemid: string;
channel: number;
weight: number;
};
interface OpResult {
status: boolean;
timeslotitemid?: string;
}
export function updateShowplan(
timeslotid: number,
ops: UpdateOp[]
): Promise<OpResult[]> {
return myradioApiRequest(`/timeslot/${timeslotid}/updateshowplan`, "PUT", {
set: ops,
});
}
export interface Timeslot {
timeslot_id: number;
time: number;
start_time: string;
title: string;
duration: string;
credits_string: string;
}
export function getCurrentApiTimeslot(): Promise<Timeslot> {
return myradioApiRequest(`/timeslot/userselectedtimeslot`, "GET", {}).then(
(res) => {
return res;
}
);
}
export interface User {
memberid: number;
fname: string;
sname: string;
url: string;
photo: string;
public_email?: string;
}
export function getCurrentApiUser(): Promise<User> {
return myradioApiRequest(`/user/currentuser`, "GET", {}).then((res) => {
return res;
});
}
export function doesCurrentUserHavePermission(id: number): Promise<boolean> {
return myradioApiRequest("/auth/haspermission/" + id.toString(10), "GET", {});
}
export interface NewsEntry {
newsentryid: string;
author: string;
posted: string;
content: string;
seen?: boolean;
}
export function getLatestNewsItem(
newsFeedId: number
): Promise<NewsEntry | null> {
return myradioApiRequest(`/news/latestnewsitem/${newsFeedId}`, "GET", {});
}