synapse-admin/src/synapse/dataProvider.ts
Manuel Stahl 4761ea36bc Update eslint for typescript
Change-Id: I39ad44666fe958dd4f6c8f0d88b3dc960d7cb6c7
2024-05-06 08:33:32 +02:00

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;