mirror of
https://github.com/UA-Fediland/synapse-admin.git
synced 2025-03-30 19:24:55 +00:00
705 lines
19 KiB
TypeScript
705 lines
19 KiB
TypeScript
import { stringify } from "query-string";
|
|
|
|
import { DataProvider, DeleteParams, Identifier, Options, RaRecord, fetchUtils } from "react-admin";
|
|
|
|
// Adds the access token to all requests
|
|
const jsonClient = (url: string, options: Options = {}) => {
|
|
const token = localStorage.getItem("access_token");
|
|
console.log("httpClient " + url);
|
|
if (token != null) {
|
|
options.user = {
|
|
authenticated: true,
|
|
token: `Bearer ${token}`,
|
|
};
|
|
}
|
|
return fetchUtils.fetchJson(url, options);
|
|
};
|
|
|
|
const mxcUrlToHttp = (mxcUrl: string) => {
|
|
const homeserver = localStorage.getItem("base_url");
|
|
const re = /^mxc:\/\/([^/]+)\/(\w+)/;
|
|
const ret = re.exec(mxcUrl);
|
|
console.log("mxcClient " + ret);
|
|
if (ret == null) return null;
|
|
const serverName = ret[1];
|
|
const mediaId = ret[2];
|
|
return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`;
|
|
};
|
|
|
|
interface Room {
|
|
room_id: string;
|
|
name?: string;
|
|
canonical_alias?: string;
|
|
avatar_url?: string;
|
|
joined_members: number;
|
|
joined_local_members: number;
|
|
version: number;
|
|
creator: string;
|
|
encryption?: string;
|
|
federatable: boolean;
|
|
public: boolean;
|
|
join_rules: "public" | "knock" | "invite" | "private";
|
|
guest_access?: "can_join" | "forbidden";
|
|
history_visibility: "invited" | "joined" | "shared" | "world_readable";
|
|
state_events: number;
|
|
room_type?: string;
|
|
}
|
|
|
|
interface RoomState {
|
|
age: number;
|
|
content: {
|
|
alias?: string;
|
|
};
|
|
event_id: string;
|
|
origin_server_ts: number;
|
|
room_id: string;
|
|
sender: string;
|
|
state_key: string;
|
|
type: string;
|
|
user_id: string;
|
|
unsigned: {
|
|
age?: number;
|
|
};
|
|
}
|
|
|
|
interface ForwardExtremity {
|
|
event_id: string;
|
|
state_group: number;
|
|
depth: number;
|
|
received_ts: number;
|
|
}
|
|
|
|
interface EventReport {
|
|
id: number;
|
|
received_ts: number;
|
|
room_id: string;
|
|
name: string;
|
|
event_id: string;
|
|
user_id: string;
|
|
reason?: string;
|
|
score?: number;
|
|
sender: string;
|
|
canonical_alias?: string;
|
|
}
|
|
|
|
interface Threepid {
|
|
medium: string;
|
|
address: string;
|
|
added_at: number;
|
|
validated_at: number;
|
|
}
|
|
|
|
interface ExternalId {
|
|
auth_provider: string;
|
|
external_id: string;
|
|
}
|
|
|
|
interface User {
|
|
name: string;
|
|
displayname?: string;
|
|
threepids: Threepid[];
|
|
avatar_url?: string;
|
|
is_guest: 0 | 1;
|
|
admin: 0 | 1;
|
|
deactivated: 0 | 1;
|
|
erased: boolean;
|
|
shadow_banned: 0 | 1;
|
|
creation_ts: number;
|
|
appservice_id?: string;
|
|
consent_server_notice_sent?: string;
|
|
consent_version?: string;
|
|
consent_ts?: number;
|
|
external_ids: ExternalId[];
|
|
user_type?: string;
|
|
locked: boolean;
|
|
}
|
|
|
|
interface Device {
|
|
device_id: string;
|
|
display_name?: string;
|
|
last_seen_ip?: string;
|
|
last_seen_user_agent?: string;
|
|
last_seen_ts?: number;
|
|
user_id: string;
|
|
}
|
|
|
|
interface Connection {
|
|
ip: string;
|
|
last_seen: number;
|
|
user_agent: string;
|
|
}
|
|
|
|
interface Whois {
|
|
user_id: string;
|
|
devices: Record<
|
|
string,
|
|
{
|
|
sessions: {
|
|
connections: Connection[];
|
|
}[];
|
|
}
|
|
>;
|
|
}
|
|
|
|
interface Pusher {
|
|
app_display_name: string;
|
|
app_id: string;
|
|
data: {
|
|
url?: string;
|
|
format: string;
|
|
};
|
|
url: string;
|
|
format: string;
|
|
device_display_name: string;
|
|
profile_tag: string;
|
|
kind: string;
|
|
lang: string;
|
|
pushkey: string;
|
|
}
|
|
|
|
interface UserMedia {
|
|
created_ts: number;
|
|
last_access_ts?: number;
|
|
media_id: string;
|
|
media_length: number;
|
|
media_type: string;
|
|
quarantined_by?: string;
|
|
safe_from_quarantine: boolean;
|
|
upload_name?: string;
|
|
}
|
|
|
|
interface UserMediaStatistic {
|
|
displayname: string;
|
|
media_count: number;
|
|
media_length: number;
|
|
user_id: string;
|
|
}
|
|
|
|
interface RegistrationToken {
|
|
token: string;
|
|
uses_allowed: number;
|
|
pending: number;
|
|
completed: number;
|
|
expiry_time?: number;
|
|
}
|
|
|
|
interface RaServerNotice {
|
|
id: string;
|
|
body: string;
|
|
}
|
|
|
|
interface Destination {
|
|
destination: string;
|
|
retry_last_ts: number;
|
|
retry_interval: number;
|
|
failure_ts: number;
|
|
last_successful_stream_ordering?: number;
|
|
}
|
|
|
|
interface DestinationRoom {
|
|
room_id: string;
|
|
stream_ordering: number;
|
|
}
|
|
|
|
const resourceMap = {
|
|
users: {
|
|
path: "/_synapse/admin/v2/users",
|
|
map: (u: User) => ({
|
|
...u,
|
|
id: u.name,
|
|
avatar_src: u.avatar_url ? mxcUrlToHttp(u.avatar_url) : undefined,
|
|
is_guest: !!u.is_guest,
|
|
admin: !!u.admin,
|
|
deactivated: !!u.deactivated,
|
|
// need timestamp in milliseconds
|
|
creation_ts_ms: u.creation_ts * 1000,
|
|
}),
|
|
data: "users",
|
|
total: json => json.total,
|
|
create: (data: RaRecord) => ({
|
|
endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(data.id)}:${localStorage.getItem("home_server")}`,
|
|
body: data,
|
|
method: "PUT",
|
|
}),
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(params.id)}`,
|
|
body: { erase: true },
|
|
method: "POST",
|
|
}),
|
|
},
|
|
rooms: {
|
|
path: "/_synapse/admin/v1/rooms",
|
|
map: (r: Room) => ({
|
|
...r,
|
|
id: r.room_id,
|
|
alias: r.canonical_alias,
|
|
members: r.joined_members,
|
|
is_encrypted: !!r.encryption,
|
|
federatable: !!r.federatable,
|
|
public: !!r.public,
|
|
}),
|
|
data: "rooms",
|
|
total: json => json.total_rooms,
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
|
|
body: { block: false },
|
|
}),
|
|
},
|
|
reports: {
|
|
path: "/_synapse/admin/v1/event_reports",
|
|
map: (er: EventReport) => ({ ...er }),
|
|
data: "event_reports",
|
|
total: json => json.total,
|
|
},
|
|
devices: {
|
|
map: (d: Device) => ({
|
|
...d,
|
|
id: d.device_id,
|
|
}),
|
|
data: "devices",
|
|
total: json => json.total,
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(id)}/devices`,
|
|
}),
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(params.previousData.user_id)}/devices/${params.id}`,
|
|
}),
|
|
},
|
|
connections: {
|
|
path: "/_synapse/admin/v1/whois",
|
|
map: (c: Whois) => ({
|
|
...c,
|
|
id: c.user_id,
|
|
}),
|
|
data: "connections",
|
|
},
|
|
room_members: {
|
|
map: (m: string) => ({
|
|
id: m,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/rooms/${id}/members`,
|
|
}),
|
|
data: "members",
|
|
total: json => json.total,
|
|
},
|
|
room_state: {
|
|
map: (rs: RoomState) => ({
|
|
...rs,
|
|
id: rs.event_id,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/rooms/${id}/state`,
|
|
}),
|
|
data: "state",
|
|
total: json => json.state.length,
|
|
},
|
|
pushers: {
|
|
map: (p: Pusher) => ({
|
|
...p,
|
|
id: p.pushkey,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/pushers`,
|
|
}),
|
|
data: "pushers",
|
|
total: json => json.total,
|
|
},
|
|
joined_rooms: {
|
|
map: (jr: string) => ({
|
|
id: jr,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/joined_rooms`,
|
|
}),
|
|
data: "joined_rooms",
|
|
total: json => json.total,
|
|
},
|
|
users_media: {
|
|
map: (um: UserMedia) => ({
|
|
...um,
|
|
id: um.media_id,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`,
|
|
}),
|
|
data: "media",
|
|
total: json => json.total,
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem("home_server")}/${params.id}`,
|
|
}),
|
|
},
|
|
delete_media: {
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem(
|
|
"home_server"
|
|
)}/delete?before_ts=${params.meta.before_ts}&size_gt=${
|
|
params.meta.size_gt
|
|
}&keep_profiles=${params.meta.keep_profiles}`,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
protect_media: {
|
|
map: (pm: UserMedia) => ({ id: pm.media_id }),
|
|
create: (params: UserMedia) => ({
|
|
endpoint: `/_synapse/admin/v1/media/protect/${params.media_id}`,
|
|
method: "POST",
|
|
}),
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/media/unprotect/${params.id}`,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
quarantine_media: {
|
|
map: (qm: UserMedia) => ({ id: qm.media_id }),
|
|
create: (params: UserMedia) => ({
|
|
endpoint: `/_synapse/admin/v1/media/quarantine/${localStorage.getItem("home_server")}/${params.media_id}`,
|
|
method: "POST",
|
|
}),
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/media/unquarantine/${localStorage.getItem("home_server")}/${params.id}`,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
servernotices: {
|
|
map: (n: { event_id: string }) => ({ id: n.event_id }),
|
|
create: (data: RaServerNotice) => ({
|
|
endpoint: "/_synapse/admin/v1/send_server_notice",
|
|
body: {
|
|
user_id: data.id,
|
|
content: {
|
|
msgtype: "m.text",
|
|
body: data.body,
|
|
},
|
|
},
|
|
method: "POST",
|
|
}),
|
|
},
|
|
user_media_statistics: {
|
|
path: "/_synapse/admin/v1/statistics/users/media",
|
|
map: (usms: UserMediaStatistic) => ({
|
|
...usms,
|
|
id: usms.user_id,
|
|
}),
|
|
data: "users",
|
|
total: json => json.total,
|
|
},
|
|
forward_extremities: {
|
|
map: (fe: ForwardExtremity) => ({
|
|
...fe,
|
|
id: fe.event_id,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/rooms/${id}/forward_extremities`,
|
|
}),
|
|
data: "results",
|
|
total: json => json.count,
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/rooms/${params.id}/forward_extremities`,
|
|
}),
|
|
},
|
|
room_directory: {
|
|
path: "/_matrix/client/r0/publicRooms",
|
|
map: (rd: Room) => ({
|
|
...rd,
|
|
id: rd.room_id,
|
|
public: !!rd.public,
|
|
guest_access: !!rd.guest_access,
|
|
avatar_src: rd.avatar_url ? mxcUrlToHttp(rd.avatar_url) : undefined,
|
|
}),
|
|
data: "chunk",
|
|
total: json => json.total_room_count_estimate,
|
|
create: (params: RaRecord) => ({
|
|
endpoint: `/_matrix/client/r0/directory/list/room/${params.id}`,
|
|
body: { visibility: "public" },
|
|
method: "PUT",
|
|
}),
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_matrix/client/r0/directory/list/room/${params.id}`,
|
|
body: { visibility: "private" },
|
|
method: "PUT",
|
|
}),
|
|
},
|
|
destinations: {
|
|
path: "/_synapse/admin/v1/federation/destinations",
|
|
map: (dst: Destination) => ({
|
|
...dst,
|
|
id: dst.destination,
|
|
}),
|
|
data: "destinations",
|
|
total: json => json.total,
|
|
delete: params => ({
|
|
endpoint: `/_synapse/admin/v1/federation/destinations/${params.id}/reset_connection`,
|
|
method: "POST",
|
|
}),
|
|
},
|
|
destination_rooms: {
|
|
map: (dstroom: DestinationRoom) => ({
|
|
...dstroom,
|
|
id: dstroom.room_id,
|
|
}),
|
|
reference: (id: Identifier) => ({
|
|
endpoint: `/_synapse/admin/v1/federation/destinations/${id}/rooms`,
|
|
}),
|
|
data: "rooms",
|
|
total: json => json.total,
|
|
},
|
|
registration_tokens: {
|
|
path: "/_synapse/admin/v1/registration_tokens",
|
|
map: (rt: RegistrationToken) => ({
|
|
...rt,
|
|
id: rt.token,
|
|
}),
|
|
data: "registration_tokens",
|
|
total: json => json.registration_tokens.length,
|
|
create: (params: RaRecord) => ({
|
|
endpoint: "/_synapse/admin/v1/registration_tokens/new",
|
|
body: params,
|
|
method: "POST",
|
|
}),
|
|
delete: (params: DeleteParams) => ({
|
|
endpoint: `/_synapse/admin/v1/registration_tokens/${params.id}`,
|
|
}),
|
|
},
|
|
};
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
function filterNullValues(key: string, value: any) {
|
|
// Filtering out null properties
|
|
// to reset user_type from user, it must be null
|
|
if (value === null && key !== "user_type") {
|
|
return undefined;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function getSearchOrder(order: "ASC" | "DESC") {
|
|
if (order === "DESC") {
|
|
return "b";
|
|
} else {
|
|
return "f";
|
|
}
|
|
}
|
|
|
|
const dataProvider: DataProvider = {
|
|
getList: async (resource, params) => {
|
|
console.log("getList " + resource);
|
|
const { user_id, name, guests, deactivated, search_term, destination, valid } = params.filter;
|
|
const { page, perPage } = params.pagination;
|
|
const { field, order } = params.sort;
|
|
const from = (page - 1) * perPage;
|
|
const query = {
|
|
from: from,
|
|
limit: perPage,
|
|
user_id: user_id,
|
|
search_term: search_term,
|
|
name: name,
|
|
destination: destination,
|
|
guests: guests,
|
|
deactivated: deactivated,
|
|
valid: valid,
|
|
order_by: field,
|
|
dir: getSearchOrder(order),
|
|
};
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
const endpoint_url = homeserver + res.path;
|
|
const url = `${endpoint_url}?${stringify(query)}`;
|
|
|
|
const { json } = await jsonClient(url);
|
|
return {
|
|
data: json[res.data].map(res.map),
|
|
total: res.total(json, from, perPage),
|
|
};
|
|
},
|
|
|
|
getOne: async (resource, params) => {
|
|
console.log("getOne " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
const endpoint_url = homeserver + res.path;
|
|
const { json } = await jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`);
|
|
return { data: res.map(json) };
|
|
},
|
|
|
|
getMany: async (resource, params) => {
|
|
console.log("getMany " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homerserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
const endpoint_url = homeserver + res.path;
|
|
const responses = await Promise.all(params.ids.map(id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)));
|
|
return {
|
|
data: responses.map(({ json }) => res.map(json)),
|
|
total: responses.length,
|
|
};
|
|
},
|
|
|
|
getManyReference: async (resource, params) => {
|
|
console.log("getManyReference " + resource);
|
|
const { page, perPage } = params.pagination;
|
|
const { field, order } = params.sort;
|
|
const from = (page - 1) * perPage;
|
|
const query = {
|
|
from: from,
|
|
limit: perPage,
|
|
order_by: field,
|
|
dir: getSearchOrder(order),
|
|
};
|
|
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
const ref = res.reference(params.id);
|
|
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`;
|
|
|
|
const { json } = await jsonClient(endpoint_url);
|
|
return {
|
|
data: json[res.data].map(res.map),
|
|
total: res.total(json, from, perPage),
|
|
};
|
|
},
|
|
|
|
update: async (resource, params) => {
|
|
console.log("update " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
const endpoint_url = homeserver + res.path;
|
|
const { json } = await jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify(params.data, filterNullValues),
|
|
});
|
|
return { data: res.map(json) };
|
|
},
|
|
|
|
updateMany: async (resource, params) => {
|
|
console.log("updateMany " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
const endpoint_url = homeserver + res.path;
|
|
const responses = await Promise.all(
|
|
params.ids.map(id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`), {
|
|
method: "PUT",
|
|
body: JSON.stringify(params.data, filterNullValues),
|
|
})
|
|
);
|
|
return { data: responses.map(({ json }) => json) };
|
|
},
|
|
|
|
create: async (resource, params) => {
|
|
console.log("create " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
if (!("create" in res)) return Promise.reject();
|
|
|
|
const create = res.create(params.data);
|
|
const endpoint_url = homeserver + create.endpoint;
|
|
const { json } = await jsonClient(endpoint_url, {
|
|
method: create.method,
|
|
body: JSON.stringify(create.body, filterNullValues),
|
|
});
|
|
return { data: res.map(json) };
|
|
},
|
|
|
|
createMany: async (resource: string, params: { ids: Identifier[]; data: RaRecord }) => {
|
|
console.log("createMany " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
if (!("create" in res)) throw Error(`Create ${resource} is not allowed`);
|
|
|
|
const responses = await Promise.all(
|
|
params.ids.map(id => {
|
|
params.data.id = id;
|
|
const cre = res.create(params.data);
|
|
const endpoint_url = homeserver + cre.endpoint;
|
|
return jsonClient(endpoint_url, {
|
|
method: cre.method,
|
|
body: JSON.stringify(cre.body, filterNullValues),
|
|
});
|
|
})
|
|
);
|
|
return { data: responses.map(({ json }) => json) };
|
|
},
|
|
|
|
delete: async (resource, params) => {
|
|
console.log("delete " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
if ("delete" in res) {
|
|
const del = res.delete(params);
|
|
const endpoint_url = homeserver + del.endpoint;
|
|
const { json } = await jsonClient(endpoint_url, {
|
|
method: "method" in del ? del.method : "DELETE",
|
|
body: "body" in del ? JSON.stringify(del.body) : null,
|
|
});
|
|
return { data: json };
|
|
} else {
|
|
const endpoint_url = homeserver + res.path;
|
|
const { json } = await jsonClient(`${endpoint_url}/${params.id}`, {
|
|
method: "DELETE",
|
|
body: JSON.stringify(params.previousData, filterNullValues),
|
|
});
|
|
return { data: json };
|
|
}
|
|
},
|
|
|
|
deleteMany: async (resource, params) => {
|
|
console.log("deleteMany " + resource);
|
|
const homeserver = localStorage.getItem("base_url");
|
|
if (!homeserver || !(resource in resourceMap)) throw Error("Homeserver not set");
|
|
|
|
const res = resourceMap[resource];
|
|
|
|
if ("delete" in res) {
|
|
const responses = await Promise.all(
|
|
params.ids.map(id => {
|
|
const del = res.delete({ ...params, id: id });
|
|
const endpoint_url = homeserver + del.endpoint;
|
|
return jsonClient(endpoint_url, {
|
|
method: "method" in del ? del.method : "DELETE",
|
|
body: "body" in del ? JSON.stringify(del.body) : null,
|
|
});
|
|
})
|
|
);
|
|
return {
|
|
data: responses.map(({ json }) => json),
|
|
};
|
|
} else {
|
|
const endpoint_url = homeserver + res.path;
|
|
const responses = await Promise.all(
|
|
params.ids.map(id =>
|
|
jsonClient(`${endpoint_url}/${id}`, {
|
|
method: "DELETE",
|
|
// body: JSON.stringify(params.data, filterNullValues), @FIXME
|
|
})
|
|
)
|
|
);
|
|
return { data: responses.map(({ json }) => json) };
|
|
}
|
|
},
|
|
};
|
|
|
|
export default dataProvider;
|