From aaf3f499dde58c834187a596c4587620d7f844b5 Mon Sep 17 00:00:00 2001 From: ramiroaisen <52116153+ramiroaisen@users.noreply.github.com> Date: Tue, 12 Mar 2024 02:14:47 -0300 Subject: [PATCH] feat: add stream connection lite endpoint and use it from admin listeners --- .../GET/Output.schema.json | 352 ++++++++++++ .../api/stream-connections-lite/GET/Output.ts | 5 + .../GET/Query.schema.json | 40 ++ defs/api/stream-connections-lite/GET/Query.ts | 10 + .../GET/ShowQuery.schema.json | 8 + .../stream-connections-lite/GET/ShowQuery.ts | 3 + .../GET/SortQuery.schema.json | 7 + .../stream-connections-lite/GET/SortQuery.ts | 3 + defs/db/StreamConnectionLite.schema.json | 324 +++++++++++ defs/db/StreamConnectionLite.ts | 4 +- .../(online)/(app)/listeners/+page.server.ts | 2 +- .../(online)/(app)/listeners/+page.svelte | 143 +++-- front/packages/client/package.json | 2 +- front/packages/client/src/index.ts | 13 + front/server/package-lock.json | 14 +- front/server/package.json | 2 +- front/server/src/api/shared-api.ts | 6 + openapi.json | 518 +++++++++++++++++- openapi.ts | 79 ++- rs/packages/api/src/routes/mod.rs | 5 + .../src/routes/stream_connections_lite/mod.rs | 190 +++++++ .../src/models/stream_connection/lite/mod.rs | 26 +- 22 files changed, 1652 insertions(+), 104 deletions(-) create mode 100644 defs/api/stream-connections-lite/GET/Output.schema.json create mode 100644 defs/api/stream-connections-lite/GET/Output.ts create mode 100644 defs/api/stream-connections-lite/GET/Query.schema.json create mode 100644 defs/api/stream-connections-lite/GET/Query.ts create mode 100644 defs/api/stream-connections-lite/GET/ShowQuery.schema.json create mode 100644 defs/api/stream-connections-lite/GET/ShowQuery.ts create mode 100644 defs/api/stream-connections-lite/GET/SortQuery.schema.json create mode 100644 defs/api/stream-connections-lite/GET/SortQuery.ts create mode 100644 defs/db/StreamConnectionLite.schema.json create mode 100644 rs/packages/api/src/routes/stream_connections_lite/mod.rs diff --git a/defs/api/stream-connections-lite/GET/Output.schema.json b/defs/api/stream-connections-lite/GET/Output.schema.json new file mode 100644 index 00000000..2b67818c --- /dev/null +++ b/defs/api/stream-connections-lite/GET/Output.schema.json @@ -0,0 +1,352 @@ +{ + "type": "object", + "required": [ + "items", + "limit", + "skip", + "total" + ], + "properties": { + "total": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "skip": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "_id", + "ca", + "ip", + "op", + "st" + ], + "properties": { + "_id": { + "type": "string" + }, + "dp": { + "type": "string", + "nullable": true + }, + "st": { + "type": "string" + }, + "op": { + "type": "boolean" + }, + "ip": { + "type": "string", + "format": "ip" + }, + "cc": { + "type": "string", + "enum": [ + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "CV", + "KH", + "CM", + "CA", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "EU", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "US", + "UM", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ], + "nullable": true + }, + "du": { + "type": "integer", + "format": "uint64", + "minimum": 0.0, + "nullable": true + }, + "by": { + "type": "integer", + "format": "uint64", + "minimum": 0.0, + "nullable": true + }, + "br": { + "type": "string", + "nullable": true + }, + "do": { + "type": "string", + "nullable": true + }, + "os": { + "type": "string", + "nullable": true + }, + "ca": { + "type": "string", + "format": "date-time" + }, + "re": { + "type": "boolean" + }, + "_m": { + "type": "boolean" + }, + "cl": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } +} \ No newline at end of file diff --git a/defs/api/stream-connections-lite/GET/Output.ts b/defs/api/stream-connections-lite/GET/Output.ts new file mode 100644 index 00000000..e919ca3a --- /dev/null +++ b/defs/api/stream-connections-lite/GET/Output.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Paged } from "../../../Paged.js"; +import type { StreamConnectionLite } from "../../../db/StreamConnectionLite.js"; + +export type Output = Paged; diff --git a/defs/api/stream-connections-lite/GET/Query.schema.json b/defs/api/stream-connections-lite/GET/Query.schema.json new file mode 100644 index 00000000..0b760bc3 --- /dev/null +++ b/defs/api/stream-connections-lite/GET/Query.schema.json @@ -0,0 +1,40 @@ +{ + "type": "object", + "properties": { + "show": { + "type": "string", + "enum": [ + "all", + "open", + "closed" + ], + "nullable": true + }, + "sort": { + "type": "string", + "enum": [ + "creation-asc", + "creation-desc" + ], + "nullable": true + }, + "stations": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "skip": { + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "default": 60, + "type": "integer", + "format": "int64" + } + } +} \ No newline at end of file diff --git a/defs/api/stream-connections-lite/GET/Query.ts b/defs/api/stream-connections-lite/GET/Query.ts new file mode 100644 index 00000000..e6b5fc0f --- /dev/null +++ b/defs/api/stream-connections-lite/GET/Query.ts @@ -0,0 +1,10 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PaginationQs } from "../../../qs/PaginationQs.js"; +import type { ShowQuery } from "./ShowQuery.js"; +import type { SortQuery } from "./SortQuery.js"; + +export type Query = { + show?: ShowQuery; + sort?: SortQuery; + stations?: Array; +} & PaginationQs; diff --git a/defs/api/stream-connections-lite/GET/ShowQuery.schema.json b/defs/api/stream-connections-lite/GET/ShowQuery.schema.json new file mode 100644 index 00000000..ef6ec30a --- /dev/null +++ b/defs/api/stream-connections-lite/GET/ShowQuery.schema.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "enum": [ + "all", + "open", + "closed" + ] +} \ No newline at end of file diff --git a/defs/api/stream-connections-lite/GET/ShowQuery.ts b/defs/api/stream-connections-lite/GET/ShowQuery.ts new file mode 100644 index 00000000..0850ff5b --- /dev/null +++ b/defs/api/stream-connections-lite/GET/ShowQuery.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ShowQuery = "all" | "open" | "closed"; diff --git a/defs/api/stream-connections-lite/GET/SortQuery.schema.json b/defs/api/stream-connections-lite/GET/SortQuery.schema.json new file mode 100644 index 00000000..7ada3fcc --- /dev/null +++ b/defs/api/stream-connections-lite/GET/SortQuery.schema.json @@ -0,0 +1,7 @@ +{ + "type": "string", + "enum": [ + "creation-asc", + "creation-desc" + ] +} \ No newline at end of file diff --git a/defs/api/stream-connections-lite/GET/SortQuery.ts b/defs/api/stream-connections-lite/GET/SortQuery.ts new file mode 100644 index 00000000..ab06d5b8 --- /dev/null +++ b/defs/api/stream-connections-lite/GET/SortQuery.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SortQuery = "creation-asc" | "creation-desc"; diff --git a/defs/db/StreamConnectionLite.schema.json b/defs/db/StreamConnectionLite.schema.json new file mode 100644 index 00000000..a7587950 --- /dev/null +++ b/defs/db/StreamConnectionLite.schema.json @@ -0,0 +1,324 @@ +{ + "type": "object", + "required": [ + "_id", + "ca", + "ip", + "op", + "st" + ], + "properties": { + "_id": { + "type": "string" + }, + "dp": { + "type": "string", + "nullable": true + }, + "st": { + "type": "string" + }, + "op": { + "type": "boolean" + }, + "ip": { + "type": "string", + "format": "ip" + }, + "cc": { + "type": "string", + "enum": [ + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "CV", + "KH", + "CM", + "CA", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "EU", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "US", + "UM", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ], + "nullable": true + }, + "du": { + "type": "integer", + "format": "uint64", + "minimum": 0.0, + "nullable": true + }, + "by": { + "type": "integer", + "format": "uint64", + "minimum": 0.0, + "nullable": true + }, + "br": { + "type": "string", + "nullable": true + }, + "do": { + "type": "string", + "nullable": true + }, + "os": { + "type": "string", + "nullable": true + }, + "ca": { + "type": "string", + "format": "date-time" + }, + "re": { + "type": "boolean" + }, + "_m": { + "type": "boolean" + }, + "cl": { + "type": "string", + "format": "date-time", + "nullable": true + } + } +} \ No newline at end of file diff --git a/defs/db/StreamConnectionLite.ts b/defs/db/StreamConnectionLite.ts index 01d004a6..d2401693 100644 --- a/defs/db/StreamConnectionLite.ts +++ b/defs/db/StreamConnectionLite.ts @@ -15,7 +15,7 @@ export type StreamConnectionLite = { do: string | null | undefined; os: string | null | undefined; ca: DateTime; - re: boolean; - _m: boolean; + re?: boolean; + _m?: boolean; cl: DateTime | null | undefined; }; diff --git a/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts b/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts index 76ffc66a..0c8953f6 100644 --- a/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts +++ b/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts @@ -2,7 +2,7 @@ import { load_call, client } from "$lib/load"; export const load = (async ({ fetch }) => { const stream_connections = await load_call( - () => client.GET("/stream-connections", { + () => client.GET("/stream-connections-lite", { params: { query: { show: "open", diff --git a/front/admin/src/routes/(online)/(app)/listeners/+page.svelte b/front/admin/src/routes/(online)/(app)/listeners/+page.svelte index 684b1833..0b93942a 100644 --- a/front/admin/src/routes/(online)/(app)/listeners/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/listeners/+page.svelte @@ -4,12 +4,11 @@ import PageTop from "$lib/components/PageMenu/PageTop.svelte"; import { mdiConnection } from "@mdi/js"; import { now } from "$share/now"; - import { fly, slide, type TransitionConfig } from "svelte/transition"; + import { slide, type TransitionConfig } from "svelte/transition"; import { onMount } from "svelte"; import { default_logger } from "$share/logger"; import { sleep } from "$share/util"; import { afterNavigate, beforeNavigate } from "$app/navigation"; - import { qss } from "$share/qs"; import { _get } from "$share/net.client"; import { STATION_PICTURES_VERSION } from "$defs/constants"; import { page } from "$app/stores"; @@ -36,20 +35,15 @@ const get_show_items = (...args: any[]) => { let items = data.stream_connections.items; if(q_referer !== undefined) { - if(q_referer === null) { - items = items.filter(item => item.request.headers.referer == null && item.request.headers.origin == null); - } else { - const r = `//${q_referer}`; - items = items.filter(item => (item.request.headers.referer || item.request.headers.origin || "").includes(r)) - } + items = items.filter(item => item.do === q_referer); } if(q_os !== undefined) { - items = items.filter(item => item.request.user_agent.os === q_os); + items = items.filter(item => item.os === q_os); } if(q_browser !== undefined) { - items = items.filter(item => item.request.user_agent.name === q_browser); + items = items.filter(item => item.br === q_browser); } if(q_ip != null) { @@ -57,32 +51,21 @@ } if(q_deployment_id != null) { - items = items.filter(item => item.deployment_id === q_deployment_id); + items = items.filter(item => item.dp === q_deployment_id); } if(q_station_id != null) { - items = items.filter(item => item.station_id === q_station_id); + items = items.filter(item => item.st === q_station_id); } return items; } const item_station = (item: Item): typeof data.stations[number] | null => { - const id = item.station_id; + const id = item.st; return data.all_stations.find(item => item._id === id) ?? null; } - const website = (item: Item): string | null => { - const ref = item.request.headers.referer || item.request.headers.origin; - if(ref == null) return null - - try { - return new URL(ref).host; - } catch(e) {} - - return null - } - const qs = (qs: URLSearchParams | string) => { const s = String(qs); return s === "" ? "" : `?${s}` @@ -103,52 +86,57 @@ browser?: string | null | undefined, ip?: string | null, }) => { - const params = new URLSearchParams(); - deployment && params.append("deployment", deployment); - station && params.set("station", station); - os && params.set("os", os); - browser && params.set("browser", browser); - ip && params.set("ip", ip); - referer && params.set("referer", referer); - return params; + const params: Record = {}; + if(deployment) params.deployment = deployment; + if(station) params.station = station; + if(referer) params.referer = referer; + if(os) params.os = os; + if(browser) params.browser = browser; + if(ip) params.ip = ip; + + let qs = Object.entries(params).map(([k,v]) => { + return `${k}=${encodeURIComponent(v)}`; + }).join("&"); + + if(qs !== "") qs = `?${qs}`; + + return qs; } const station_toggle_link = (item: Item): string => { - return `/listeners${qs(make_params({ - station: q_station_id === item.station_id ? null : item.station_id - }))}` + return `/listeners${make_params({ + station: q_station_id === item.st ? null : item.st + })}` } const deployment_toggle_link = (item: Item): string => { - return `/listeners${qs(make_params({ - deployment: q_deployment_id === item.deployment_id ? null : item.deployment_id, - }))}` + return `/listeners${make_params({ + deployment: q_deployment_id === item.dp ? null : item.dp, + })}` } - const referer_toggle_link = (ref: string | null): string => { - return `/listeners${qs(make_params({ - referer: q_referer === ref ? "" : ref === null ? "null" : ref - }))}` + const referer_toggle_link = (item: Item): string => { + return `/listeners${make_params({ + referer: q_referer === item.do ? null : item.do === null ? "null" : item.do, + })}` } const ip_toggle_link = (item: Item): string => { - return `/listeners${qs(make_params({ + return `/listeners${make_params({ ip: q_ip === item.ip ? null : item.ip - }))}` + })}` } const os_toggle_link = (item: Item): string => { - const v = item.request.user_agent.os; - return `/listeners${qs(make_params({ - os: q_os === v ? "" : v === null ? "null" : v, - }))}` + return `/listeners${make_params({ + os: q_os === item.os ? null : item.os === null ? "null" : item.os, + })}` } const browser_toggle_link = (item: Item): string => { - const v = item.request.user_agent.name; - return `/listeners${qs(make_params({ - browser: q_browser === v ? "" : v === null ? "null" : v, - }))}` + return `/listeners${make_params({ + browser: q_browser === item.br ? null : item.br === null ? "null" : item.br, + })}` } let navigating = false; @@ -184,7 +172,7 @@ const HOUR = MIN * 60; const DAY = HOUR * 24; const duration = (item: Item, $now: Date): string => { - const ms = item.duration_ms != null ? item.duration_ms : (+$now - +new Date(item.created_at)); + const ms = item.du != null ? item.du : (+$now - +new Date(item.ca)); if(ms >= DAY) { const d = Math.floor(ms / DAY); const h = Math.floor((ms % DAY) / HOUR); @@ -247,7 +235,7 @@ try { const _token = ++token; - const stream_connections = unwrap(await GET("/stream-connections", { + const stream_connections = unwrap(await GET("/stream-connections-lite", { params: { query: { show: "open", @@ -386,11 +374,11 @@ +
{#each show_items as item (item._id)} {@const station = item_station(item)} - {@const referer = website(item)} -
+
- {#if q_station_id === item.station_id} + {#if q_station_id === item.st} « {/if} {#if station != null} {station.name} {:else} - #{item.station_id} + #{item.st} {/if}
-
- {#if q_deployment_id === item.deployment_id} + {#if q_deployment_id === item.dp} « {/if} - Deployment #{item.deployment_id} + Deployment #{item.dp} - - {#if q_referer === referer} + + {#if q_referer === item.do} « {/if} - {referer ?? "Unknown referer"} + {item.do ?? "Unknown referer"}
{duration(item, $now)} diff --git a/front/packages/client/package.json b/front/packages/client/package.json index 5c600dfa..bc767736 100644 --- a/front/packages/client/package.json +++ b/front/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@openstream/client", - "version": "0.2.0", + "version": "0.3.1", "description": "Openstream Radio Server Client", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/front/packages/client/src/index.ts b/front/packages/client/src/index.ts index c3a17d52..387b96dc 100644 --- a/front/packages/client/src/index.ts +++ b/front/packages/client/src/index.ts @@ -36,6 +36,7 @@ export class Client { invitations: AccountInvitations; payment_methods: PaymentMethods; stream_connections: StreamConnections; + stream_connections_lite: StreamConnectionsLite; constructor(base_url: string, { /*ogger,*/ fetch = node_fetch }: { /*logger: Logger,*/ fetch?: typeof node_fetch } = {}) { this.base_url = base_url.trim().replace(/\/+$/g, "") @@ -55,6 +56,7 @@ export class Client { this.invitations = new AccountInvitations(this); this.payment_methods = new PaymentMethods(this); this.stream_connections = new StreamConnections(this); + this.stream_connections_lite = new StreamConnectionsLite(this); } async fetch(_url: string, init: RequestInit = {}): Promise { @@ -725,6 +727,17 @@ export class StreamConnections { } } +export class StreamConnectionsLite { + client: Client; + constructor(client: Client) { + this.client = client; + } + + async list(ip: string | null, ua: string | null, token: string, query: import("./defs/api/stream-connections-lite/GET/Query.js").Query): Promise { + return await this.client.get(ip, ua, token, `/stream-connections-lite${qss(query)}`); + } +} + export class AccountInvitations { client: Client; diff --git a/front/server/package-lock.json b/front/server/package-lock.json index ba93ca8d..d1754b25 100644 --- a/front/server/package-lock.json +++ b/front/server/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@openstream/client": "^0.2.0", + "@openstream/client": "^0.3.1", "accept-language-parser": "^1.5.0", "braintree": "^3.15.0", "commander": "^9.4.1", @@ -278,9 +278,9 @@ } }, "node_modules/@openstream/client": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@openstream/client/-/client-0.2.0.tgz", - "integrity": "sha512-y7RwzFxvc24s7u1SdlNZnpUusgvnmuqueGfYhlyrbnm7iHhP5ddzuYGYNfVhoqPvtz5cyF9mq8dDNCDY2KA5QQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@openstream/client/-/client-0.3.1.tgz", + "integrity": "sha512-InrNIVen1mc2/0PXtmtSgp3mM2AgpYvl9ykYad7UV7WV6Lk7PJVv2nCt2RkeMpsPx7+2Zo8/o6vImfkZUluL0Q==", "dependencies": { "http-status-codes": "^2.3.0", "node-fetch": "^3.3.2", @@ -4704,9 +4704,9 @@ } }, "@openstream/client": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@openstream/client/-/client-0.2.0.tgz", - "integrity": "sha512-y7RwzFxvc24s7u1SdlNZnpUusgvnmuqueGfYhlyrbnm7iHhP5ddzuYGYNfVhoqPvtz5cyF9mq8dDNCDY2KA5QQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@openstream/client/-/client-0.3.1.tgz", + "integrity": "sha512-InrNIVen1mc2/0PXtmtSgp3mM2AgpYvl9ykYad7UV7WV6Lk7PJVv2nCt2RkeMpsPx7+2Zo8/o6vImfkZUluL0Q==", "requires": { "http-status-codes": "^2.3.0", "node-fetch": "^3.3.2", diff --git a/front/server/package.json b/front/server/package.json index 16ca0f84..7942b1ba 100644 --- a/front/server/package.json +++ b/front/server/package.json @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@openstream/client": "^0.2.0", + "@openstream/client": "^0.3.1", "accept-language-parser": "^1.5.0", "braintree": "^3.15.0", "commander": "^9.4.1", diff --git a/front/server/src/api/shared-api.ts b/front/server/src/api/shared-api.ts index 45917330..69001cb3 100644 --- a/front/server/src/api/shared-api.ts +++ b/front/server/src/api/shared-api.ts @@ -390,5 +390,11 @@ export const shared_api = ({ return await client.stream_connections.list(ip(req), ua(req), get_token(req), req.query as any); })) + api.route("/stream-connections-lite") + .get(json(async req => { + return await client.stream_connections_lite.list(ip(req), ua(req), get_token(req), req.query as any); + })) + + return api; } \ No newline at end of file diff --git a/openapi.json b/openapi.json index a82dc515..7279ff23 100644 --- a/openapi.json +++ b/openapi.json @@ -4657,6 +4657,7 @@ "stations", "total_duration_ms", "until", + "users", "utc_offset_minutes" ], "properties": { @@ -4759,6 +4760,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -4784,7 +4790,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -4822,6 +4829,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -4855,7 +4867,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -4899,6 +4912,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -4932,7 +4950,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -5201,6 +5220,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -5234,7 +5258,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -5250,6 +5275,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -5282,7 +5312,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -5299,6 +5330,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -5332,7 +5368,8 @@ "max_concurrent_listeners", "sessions", "total_duration_ms", - "total_transfer_bytes" + "total_transfer_bytes", + "users" ], "properties": { "key": { @@ -5360,6 +5397,11 @@ "format": "uint64", "minimum": 0 }, + "users": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "total_duration_ms": { "type": "integer", "format": "uint64", @@ -21522,7 +21564,7 @@ "default": false, "type": "boolean" }, - "_manually_closed": { + "_m": { "type": "boolean" }, "request": { @@ -21994,6 +22036,468 @@ } } }, + "/stream-connections-lite": { + "get": { + "parameters": [ + { + "name": "show", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "type": "string", + "enum": [ + "all", + "open", + "closed" + ], + "nullable": true + } + }, + { + "name": "sort", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "type": "string", + "enum": [ + "creation-asc", + "creation-desc" + ], + "nullable": true + } + }, + { + "name": "stations", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + { + "name": "skip", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "style": "deepObject", + "explode": true, + "allowReserved": true, + "schema": { + "default": 60, + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "A successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "items", + "limit", + "skip", + "total" + ], + "properties": { + "total": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "skip": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "limit": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "_id", + "ca", + "ip", + "op", + "st" + ], + "properties": { + "_id": { + "type": "string" + }, + "dp": { + "type": "string", + "nullable": true + }, + "st": { + "type": "string" + }, + "op": { + "type": "boolean" + }, + "ip": { + "type": "string", + "format": "ip" + }, + "cc": { + "type": "string", + "enum": [ + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "CV", + "KH", + "CM", + "CA", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "EU", + "SV", + "GQ", + "ER", + "EE", + "SZ", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MK", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "US", + "UM", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ], + "nullable": true + }, + "du": { + "type": "integer", + "format": "uint64", + "minimum": 0, + "nullable": true + }, + "by": { + "type": "integer", + "format": "uint64", + "minimum": 0, + "nullable": true + }, + "br": { + "type": "string", + "nullable": true + }, + "do": { + "type": "string", + "nullable": true + }, + "os": { + "type": "string", + "nullable": true + }, + "ca": { + "type": "string", + "format": "date-time" + }, + "re": { + "type": "boolean" + }, + "_m": { + "type": "boolean" + }, + "cl": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + } + } + } + }, + "4XX": { + "description": "A client error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "5XX": { + "description": "A server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, "/stream-stats": { "get": { "parameters": [], diff --git a/openapi.ts b/openapi.ts index 2b7e10a4..1b46fb4e 100644 --- a/openapi.ts +++ b/openapi.ts @@ -1457,6 +1457,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ max_concurrent_listeners: number; @@ -1476,6 +1478,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ total_transfer_bytes: number; @@ -1500,6 +1504,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ total_transfer_bytes: number; @@ -1516,6 +1522,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ total_transfer_bytes: number; @@ -1531,6 +1539,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ total_transfer_bytes: number; @@ -1546,6 +1556,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ total_transfer_bytes: number; @@ -1565,6 +1577,8 @@ export interface paths { /** Format: uint64 */ ips: number; /** Format: uint64 */ + users: number; + /** Format: uint64 */ total_duration_ms: number; /** Format: uint64 */ total_transfer_bytes: number; @@ -5455,7 +5469,7 @@ export interface paths { ip: string; /** @default false */ is_external_relay_redirect?: boolean; - _manually_closed?: boolean; + _m?: boolean; request: { /** Format: ip */ real_ip: string; @@ -5525,6 +5539,69 @@ export interface paths { }; }; }; + "/stream-connections-lite": { + get: { + parameters: { + query?: { + show?: "all" | "open" | "closed" | null; + sort?: "creation-asc" | "creation-desc" | null; + stations?: string[] | null; + skip?: number; + limit?: number; + }; + }; + responses: { + /** @description A successful response */ + 200: { + content: { + "application/json": { + /** Format: uint64 */ + total: number; + /** Format: uint64 */ + skip: number; + /** Format: int64 */ + limit: number; + items: ({ + _id: string; + dp?: string | null; + st: string; + op: boolean; + /** Format: ip */ + ip: string; + /** @enum {string|null} */ + cc?: "AF" | "AX" | "AL" | "DZ" | "AS" | "AD" | "AO" | "AI" | "AQ" | "AG" | "AR" | "AM" | "AW" | "AU" | "AT" | "AZ" | "BS" | "BH" | "BD" | "BB" | "BY" | "BE" | "BZ" | "BJ" | "BM" | "BT" | "BO" | "BQ" | "BA" | "BW" | "BV" | "BR" | "IO" | "BN" | "BG" | "BF" | "BI" | "CV" | "KH" | "CM" | "CA" | "KY" | "CF" | "TD" | "CL" | "CN" | "CX" | "CC" | "CO" | "KM" | "CG" | "CD" | "CK" | "CR" | "CI" | "HR" | "CU" | "CW" | "CY" | "CZ" | "DK" | "DJ" | "DM" | "DO" | "EC" | "EG" | "EU" | "SV" | "GQ" | "ER" | "EE" | "SZ" | "ET" | "FK" | "FO" | "FJ" | "FI" | "FR" | "GF" | "PF" | "TF" | "GA" | "GM" | "GE" | "DE" | "GH" | "GI" | "GR" | "GL" | "GD" | "GP" | "GU" | "GT" | "GG" | "GN" | "GW" | "GY" | "HT" | "HM" | "VA" | "HN" | "HK" | "HU" | "IS" | "IN" | "ID" | "IR" | "IQ" | "IE" | "IM" | "IL" | "IT" | "JM" | "JP" | "JE" | "JO" | "KZ" | "KE" | "KI" | "KP" | "KR" | "KW" | "KG" | "LA" | "LV" | "LB" | "LS" | "LR" | "LY" | "LI" | "LT" | "LU" | "MO" | "MG" | "MW" | "MY" | "MV" | "ML" | "MT" | "MH" | "MQ" | "MR" | "MU" | "YT" | "MX" | "FM" | "MD" | "MC" | "MN" | "ME" | "MS" | "MA" | "MZ" | "MM" | "NA" | "NR" | "NP" | "NL" | "NC" | "NZ" | "NI" | "NE" | "NG" | "NU" | "NF" | "MK" | "MP" | "NO" | "OM" | "PK" | "PW" | "PS" | "PA" | "PG" | "PY" | "PE" | "PH" | "PN" | "PL" | "PT" | "PR" | "QA" | "RE" | "RO" | "RU" | "RW" | "BL" | "SH" | "KN" | "LC" | "MF" | "PM" | "VC" | "WS" | "SM" | "ST" | "SA" | "SN" | "RS" | "SC" | "SL" | "SG" | "SX" | "SK" | "SI" | "SB" | "SO" | "ZA" | "GS" | "SS" | "ES" | "LK" | "SD" | "SR" | "SJ" | "SE" | "CH" | "SY" | "TW" | "TJ" | "TZ" | "TH" | "TL" | "TG" | "TK" | "TO" | "TT" | "TN" | "TR" | "TM" | "TC" | "TV" | "UG" | "UA" | "AE" | "GB" | "US" | "UM" | "UY" | "UZ" | "VU" | "VE" | "VN" | "VG" | "VI" | "WF" | "EH" | "YE" | "ZM" | "ZW" | null; + /** Format: uint64 */ + du?: number | null; + /** Format: uint64 */ + by?: number | null; + br?: string | null; + do?: string | null; + os?: string | null; + /** Format: date-time */ + ca: string; + re?: boolean; + _m?: boolean; + /** Format: date-time */ + cl?: string | null; + })[]; + }; + }; + }; + /** @description A client error */ + "4XX": { + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description A server error */ + "5XX": { + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + }; + }; "/stream-stats": { get: { responses: { diff --git a/rs/packages/api/src/routes/mod.rs b/rs/packages/api/src/routes/mod.rs index 96d87624..52acbfcb 100644 --- a/rs/packages/api/src/routes/mod.rs +++ b/rs/packages/api/src/routes/mod.rs @@ -14,6 +14,7 @@ pub mod plans; pub mod runtime; pub mod station_pictures; pub mod stream_connections; +pub mod stream_connections_lite; pub mod stream_stats; use db::station_picture::StationPicture; @@ -543,6 +544,10 @@ pub fn router( .at("/stream-connections") .get(stream_connections::get::Endpoint {}.into_handler()); + app + .at("/stream-connections-lite") + .get(stream_connections_lite::get::Endpoint {}.into_handler()); + // 404 catch all app.with(ResourceNotFound.into_handler()); diff --git a/rs/packages/api/src/routes/stream_connections_lite/mod.rs b/rs/packages/api/src/routes/stream_connections_lite/mod.rs new file mode 100644 index 00000000..f6132a08 --- /dev/null +++ b/rs/packages/api/src/routes/stream_connections_lite/mod.rs @@ -0,0 +1,190 @@ +pub mod get { + + use async_trait::async_trait; + use db::station::Station; + use db::stream_connection::lite::StreamConnectionLite; + use db::user_account_relation::UserAccountRelation; + use db::Model; + use db::Paged; + use mongodb::bson::doc; + use prex::Request; + use schemars::JsonSchema; + use serde::{Deserialize, Serialize}; + use ts_rs::TS; + + use crate::qs::PaginationQs; + use crate::request_ext::{self, AccessTokenScope, GetAccessTokenScopeError}; + use crate::{error::ApiError, json::JsonHandler}; + + #[derive(Debug, Clone)] + pub struct Endpoint {} + + #[derive(Debug, Clone, Serialize, Deserialize, TS, JsonSchema)] + #[ts(export, export_to = "../../../defs/api/stream-connections-lite/GET/")] + #[macros::schema_ts_export] + #[serde(rename_all = "kebab-case")] + pub enum ShowQuery { + All, + Open, + Closed, + } + + #[derive(Debug, Clone, Serialize, Deserialize, TS, JsonSchema)] + #[ts(export, export_to = "../../../defs/api/stream-connections-lite/GET/")] + #[macros::schema_ts_export] + #[serde(rename_all = "kebab-case")] + pub enum SortQuery { + CreationAsc, + CreationDesc, + } + + #[derive(Debug, Clone, Default, Serialize, Deserialize, TS, JsonSchema)] + #[ts(export, export_to = "../../../defs/api/stream-connections-lite/GET/")] + #[macros::schema_ts_export] + pub struct Query { + #[serde(flatten)] + pub page: PaginationQs, + #[serde(skip_serializing_if = "Option::is_none")] + pub show: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sort: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub stations: Option>, + } + + #[derive(Debug, Clone)] + pub struct Input { + pub access_token_scope: AccessTokenScope, + pub query: Query, + } + + #[derive(Debug, Clone, Serialize, Deserialize, TS, JsonSchema)] + #[ts(export, export_to = "../../../defs/api/stream-connections-lite/GET/")] + #[macros::schema_ts_export] + pub struct Output(Paged); + + #[derive(Debug, thiserror::Error)] + pub enum ParseError { + #[error("token: {0}")] + Token(#[from] GetAccessTokenScopeError), + #[error("querystring: {0}")] + QueryString(#[from] serde_qs::Error), + } + + impl From for ApiError { + fn from(e: ParseError) -> ApiError { + match e { + ParseError::Token(e) => e.into(), + ParseError::QueryString(e) => e.into(), + } + } + } + + #[derive(Debug, thiserror::Error)] + pub enum HandleError { + #[error("db: {0}")] + Db(#[from] mongodb::error::Error), + #[error("token: {0}")] + Token(#[from] GetAccessTokenScopeError), + } + + impl From for ApiError { + fn from(e: HandleError) -> ApiError { + match e { + HandleError::Db(e) => e.into(), + HandleError::Token(e) => e.into(), + } + } + } + + #[async_trait] + impl JsonHandler for Endpoint { + type Input = Input; + type Output = Output; + type ParseError = ParseError; + type HandleError = HandleError; + + async fn parse(&self, req: Request) -> Result { + let query = match req.uri().query() { + None => Default::default(), + Some(_) => req.qs()?, + }; + + let access_token_scope = request_ext::get_access_token_scope(&req).await?; + + Ok(Self::Input { + access_token_scope, + query, + }) + } + + async fn perform(&self, input: Input) -> Result { + let Self::Input { + access_token_scope, + query, + } = input; + + let Query { + page: PaginationQs { skip, limit }, + show, + stations, + sort, + } = query; + + let scope_filter = match access_token_scope { + AccessTokenScope::Global | AccessTokenScope::Admin(_) => doc! {}, + AccessTokenScope::User(user) => { + let account_ids = { + let filter = doc! { UserAccountRelation::KEY_USER_ID: &user.id }; + UserAccountRelation::cl() + .distinct(UserAccountRelation::KEY_ACCOUNT_ID, filter, None) + .await? + }; + + let scope_station_ids = { + let filter = doc! { UserAccountRelation::KEY_ACCOUNT_ID: { "$in": account_ids } }; + Station::cl() + .distinct(Station::KEY_ID, filter, None) + .await? + }; + + doc! { StreamConnectionLite::KEY_STATION_ID: { "$in": scope_station_ids } } + } + }; + + let stations_query_filter = match stations { + None => doc! {}, + Some(ids) => doc! { StreamConnectionLite::KEY_STATION_ID: { "$in": ids } }, + }; + + let show_filter = match show { + None | Some(ShowQuery::All) => { + doc! {} + } + Some(ShowQuery::Open) => { + doc! { StreamConnectionLite::KEY_IS_OPEN: true } + } + Some(ShowQuery::Closed) => { + doc! { StreamConnectionLite::KEY_IS_OPEN: false } + } + }; + + let filter = doc! { "$and": [ show_filter, scope_filter, stations_query_filter ] }; + + let sort = match sort { + None | Some(SortQuery::CreationDesc) => doc! { StreamConnectionLite::KEY_CREATED_AT: -1 }, + Some(SortQuery::CreationAsc) => doc! { StreamConnectionLite::KEY_CREATED_AT: 1 }, + }; + + let hint = match show { + None | Some(ShowQuery::All | ShowQuery::Closed) => None, + Some(ShowQuery::Open) => Some(doc! { StreamConnectionLite::KEY_IS_OPEN: 1 }), + }; + + let page = + StreamConnectionLite::paged_with_optional_hint(filter, sort, skip, limit, hint).await?; + + Ok(Output(page)) + } + } +} diff --git a/rs/packages/db/src/models/stream_connection/lite/mod.rs b/rs/packages/db/src/models/stream_connection/lite/mod.rs index 039448c7..afc57336 100644 --- a/rs/packages/db/src/models/stream_connection/lite/mod.rs +++ b/rs/packages/db/src/models/stream_connection/lite/mod.rs @@ -3,6 +3,7 @@ use crate::Model; use geoip::CountryCode; use mongodb::bson::doc; use mongodb::IndexModel; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_util::DateTime; use std::net::IpAddr; @@ -16,9 +17,10 @@ fn is_false(v: &bool) -> bool { *v == false } -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[ts(export, export_to = "../../../defs/db/")] +#[derive(Debug, Clone, Serialize, Deserialize, TS, JsonSchema)] #[serde(rename_all = "snake_case")] +#[ts(export, export_to = "../../../defs/db/")] +#[macros::schema_ts_export] #[macros::keys] pub struct StreamConnectionLite { #[serde(rename = "_id")] @@ -35,18 +37,30 @@ pub struct StreamConnectionLite { pub is_open: bool, #[serde(rename = "ip")] - #[serde(with = "serde_util::ip")] + #[serde( + serialize_with = "serde_util::ip::serialize", + deserialize_with = "serde_util::ip::deserialize" + )] + #[ts(type = "string")] pub ip: IpAddr, #[serde(rename = "cc")] pub country_code: Option, #[serde(rename = "du")] - #[serde(with = "serde_util::as_f64::option")] + #[serde( + serialize_with = "serde_util::as_f64::option::serialize", + deserialize_with = "serde_util::as_f64::option::deserialize" + )] + #[ts(type = "number | null | undefined")] pub duration_ms: Option, #[serde(rename = "by")] - #[serde(with = "serde_util::as_f64::option")] + #[serde( + serialize_with = "serde_util::as_f64::option::serialize", + deserialize_with = "serde_util::as_f64::option::deserialize" + )] + #[ts(type = "number | null | undefined")] pub transfer_bytes: Option, #[serde(rename = "br")] @@ -63,10 +77,12 @@ pub struct StreamConnectionLite { #[serde(rename = "re")] #[serde(default, skip_serializing_if = "is_false")] + #[ts(optional)] pub is_external_relay_redirect: bool, #[serde(rename = "_m")] #[serde(default, skip_serializing_if = "is_false")] + #[ts(optional)] pub abnormally_closed: bool, #[serde(rename = "cl")]