diff --git a/defs/Device.ts b/defs/Device.ts index 6771c23d..543cc335 100644 --- a/defs/Device.ts +++ b/defs/Device.ts @@ -8,7 +8,7 @@ export type Device = { ip: string; ua: UserAgent; created_at: DateTime; - last_used_at: DateTime | null; - user_id: string | null; - admin_id: string | null; + last_used_at: DateTime | null | undefined; + user_id: string | null | undefined; + admin_id: string | null | undefined; }; diff --git a/defs/PublicAdmin.ts b/defs/PublicAdmin.ts index 4bab8b95..a991de2f 100644 --- a/defs/PublicAdmin.ts +++ b/defs/PublicAdmin.ts @@ -7,9 +7,9 @@ export type PublicAdmin = { first_name: string; last_name: string; email: string; - language: string | null; + language: string | null | undefined; system_metadata: Metadata; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/PublicPaymentMethod.ts b/defs/PublicPaymentMethod.ts index ea300b46..52220c10 100644 --- a/defs/PublicPaymentMethod.ts +++ b/defs/PublicPaymentMethod.ts @@ -7,5 +7,5 @@ export type PublicPaymentMethod = { user_id: string; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; } & PublicPaymentMethodKind; diff --git a/defs/PublicPaymentMethodKind.ts b/defs/PublicPaymentMethodKind.ts index bb129916..fdb03602 100644 --- a/defs/PublicPaymentMethodKind.ts +++ b/defs/PublicPaymentMethodKind.ts @@ -3,6 +3,6 @@ export type PublicPaymentMethodKind = { kind: "card" } & { card_type: string; last_4: string; - expiration_year: string | null; - expiration_month: string | null; + expiration_year: string | null | undefined; + expiration_month: string | null | undefined; }; diff --git a/defs/UserAgent.ts b/defs/UserAgent.ts index de39d9a8..35143462 100644 --- a/defs/UserAgent.ts +++ b/defs/UserAgent.ts @@ -1,12 +1,12 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type UserAgent = { - ua: string | null; - category: string | null; - browser_type: string | null; - vendor: string | null; - name: string | null; - version: string | null; - os: string | null; - os_version: string | null; + ua: string | null | undefined; + category: string | null | undefined; + browser_type: string | null | undefined; + vendor: string | null | undefined; + name: string | null | undefined; + version: string | null | undefined; + os: string | null | undefined; + os_version: string | null | undefined; }; diff --git a/defs/UserPublicAccount.ts b/defs/UserPublicAccount.ts index f345767e..a9099c48 100644 --- a/defs/UserPublicAccount.ts +++ b/defs/UserPublicAccount.ts @@ -6,11 +6,11 @@ import type { Metadata } from "./db/Metadata.js"; export type UserPublicAccount = { _id: string; plan_id: string; - payment_method_id: string | null; + payment_method_id: string | null | undefined; name: string; limits: AccountLimits; created_at: DateTime; updated_at: DateTime; user_metadata: Metadata; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/UserPublicStation.ts b/defs/UserPublicStation.ts index caf554e5..5a09bc6d 100644 --- a/defs/UserPublicStation.ts +++ b/defs/UserPublicStation.ts @@ -12,33 +12,33 @@ export type UserPublicStation = { picture_id: string; name: string; slug: string; - slogan: string | null; - description: string | null; + slogan: string | null | undefined; + description: string | null | undefined; type_of_content: StationTypeOfContent; country_code: CountryCode; lang_code: LangCode; - frequency: StationFrequency | null; - email: string | null; - phone: string | null; - whatsapp: string | null; - website_url: string | null; - twitter_url: string | null; - facebook_url: string | null; - instagram_url: string | null; - threads_url: string | null; - twitch_url: string | null; - tiktok_url: string | null; - youtube_url: string | null; - spotify_url: string | null; - radiocut_url: string | null; - app_store_url: string | null; - google_play_url: string | null; + frequency: StationFrequency | null | undefined; + email: string | null | undefined; + phone: string | null | undefined; + whatsapp: string | null | undefined; + website_url: string | null | undefined; + twitter_url: string | null | undefined; + facebook_url: string | null | undefined; + instagram_url: string | null | undefined; + threads_url: string | null | undefined; + twitch_url: string | null | undefined; + tiktok_url: string | null | undefined; + youtube_url: string | null | undefined; + spotify_url: string | null | undefined; + radiocut_url: string | null | undefined; + app_store_url: string | null | undefined; + google_play_url: string | null | undefined; user_metadata: Metadata; - external_relay_url: string | null; + external_relay_url: string | null | undefined; external_relay_redirect: boolean; playlist_is_randomly_shuffled: boolean; source_password: string; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/analytics/Analytics.ts b/defs/analytics/Analytics.ts index d367b93c..71ccca35 100644 --- a/defs/analytics/Analytics.ts +++ b/defs/analytics/Analytics.ts @@ -22,10 +22,10 @@ export type Analytics = { max_concurrent_listeners?: number; max_concurrent_listeners_date?: DateTime; by_day: Array>; - by_hour: Array> | null; - by_browser: Array>; - by_os: Array>; - by_country: Array>; + by_hour: Array> | null | undefined; + by_browser: Array>; + by_os: Array>; + by_country: Array>; by_station: Array>; - by_domain: Array>; + by_domain: Array>; }; diff --git a/defs/api/ApiKey.ts b/defs/api/ApiKey.ts index 40bcb595..2e9bf3f6 100644 --- a/defs/api/ApiKey.ts +++ b/defs/api/ApiKey.ts @@ -5,8 +5,8 @@ export type ApiKey = { _id: string; is_current: boolean; title: string; - user_id: string | null; - admin_id: string | null; + user_id: string | null | undefined; + admin_id: string | null | undefined; created_at: DateTime; - last_used_at: DateTime | null; + last_used_at: DateTime | null | undefined; }; diff --git a/defs/api/PublicInvitation.ts b/defs/api/PublicInvitation.ts index bc391020..e3b66c4d 100644 --- a/defs/api/PublicInvitation.ts +++ b/defs/api/PublicInvitation.ts @@ -8,16 +8,16 @@ import type { InvitationUserSender } from "./InvitationUserSender.js"; export type PublicInvitation = { id: string; - user_sender_id: string | null; - admin_sender_id: string | null; + user_sender_id: string | null | undefined; + admin_sender_id: string | null | undefined; account_id: string; receiver_email: string; created_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; is_expired: boolean; expires_at: DateTime; - account: InvitationAccount | null; - user_sender: InvitationUserSender | null; - admin_sender: InvitationAdminSender | null; - receiver: InvitationReceiver | null; + account: InvitationAccount | null | undefined; + user_sender: InvitationUserSender | null | undefined; + admin_sender: InvitationAdminSender | null | undefined; + receiver: InvitationReceiver | null | undefined; } & AccountInvitationState; diff --git a/defs/api/analytics/GET/Query.ts b/defs/api/analytics/GET/Query.ts index b071be77..24bb1665 100644 --- a/defs/api/analytics/GET/Query.ts +++ b/defs/api/analytics/GET/Query.ts @@ -5,9 +5,9 @@ import type { CountryCodeOrZZ } from "./CountryCodeOrZZ.js"; export type Query = { kind: AnalyticsQueryKind; stations: string[] | undefined; - browser: string | null; - os: string | null; - domain: string | null; - country_code: CountryCodeOrZZ | null; + browser: string | null | undefined; + os: string | null | undefined; + domain: string | null | undefined; + country_code: CountryCodeOrZZ | null | undefined; min_duration_ms?: number; }; diff --git a/defs/api/app-analytics/GET/Query.ts b/defs/api/app-analytics/GET/Query.ts index dbafe688..c779ad03 100644 --- a/defs/api/app-analytics/GET/Query.ts +++ b/defs/api/app-analytics/GET/Query.ts @@ -5,8 +5,8 @@ import type { CountryCodeOrZZ } from "./CountryCodeOrZZ.js"; export type Query = { kind: AnalyticsQueryKind; stations: string[] | undefined; - app_kind: string | null; - app_version: string | null; - country_code: CountryCodeOrZZ | null; + app_kind: string | null | undefined; + app_version: string | null | undefined; + country_code: CountryCodeOrZZ | null | undefined; min_duration_ms?: number; }; diff --git a/defs/api/auth/user/register/POST/Payload.ts b/defs/api/auth/user/register/POST/Payload.ts index 98b3169d..20bb599b 100644 --- a/defs/api/auth/user/register/POST/Payload.ts +++ b/defs/api/auth/user/register/POST/Payload.ts @@ -8,7 +8,7 @@ export type Payload = { plan_id: string; email: string; password: string; - phone: string | null; + phone: string | null | undefined; language?: string; user_user_metadata?: Metadata; user_system_metadata?: Metadata; diff --git a/defs/api/invitations/accept/POST/UnauthenticatedAcceptPayloadData.ts b/defs/api/invitations/accept/POST/UnauthenticatedAcceptPayloadData.ts index 599424a4..7310af3d 100644 --- a/defs/api/invitations/accept/POST/UnauthenticatedAcceptPayloadData.ts +++ b/defs/api/invitations/accept/POST/UnauthenticatedAcceptPayloadData.ts @@ -4,6 +4,6 @@ export type UnauthenticatedAcceptPayloadData = { token: string; first_name: string; last_name: string; - phone: string | null; + phone: string | null | undefined; password: string; }; diff --git a/defs/api/me/api-keys/[id]/PATCH/Payload.ts b/defs/api/me/api-keys/[id]/PATCH/Payload.ts index b3bc5539..2e93b09c 100644 --- a/defs/api/me/api-keys/[id]/PATCH/Payload.ts +++ b/defs/api/me/api-keys/[id]/PATCH/Payload.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type Payload = { title: string | null }; +export type Payload = { title: string | null | undefined }; diff --git a/defs/api/plans/GET/Query.schema.json b/defs/api/plans/GET/Query.schema.json index b7293415..705cf495 100644 --- a/defs/api/plans/GET/Query.schema.json +++ b/defs/api/plans/GET/Query.schema.json @@ -1,5 +1,4 @@ { - "description": "TODO: add pagination", "type": "object", "properties": { "skip": { diff --git a/defs/api/stations/POST/Payload.ts b/defs/api/stations/POST/Payload.ts index 122be19f..a05eca39 100644 --- a/defs/api/stations/POST/Payload.ts +++ b/defs/api/stations/POST/Payload.ts @@ -9,28 +9,28 @@ export type Payload = { account_id: string; picture_id: string; name: string; - slogan: string | null; - description: string | null; + slogan: string | null | undefined; + description: string | null | undefined; type_of_content: StationTypeOfContent; country_code: CountryCode; lang_code: LangCode; - email: string | null; - phone: string | null; - whatsapp: string | null; - website_url: string | null; - twitter_url: string | null; - facebook_url: string | null; - instagram_url: string | null; - threads_url: string | null; - youtube_url: string | null; - twitch_url: string | null; - tiktok_url: string | null; - spotify_url: string | null; - radiocut_url: string | null; - google_play_url: string | null; - app_store_url: string | null; - frequency: StationFrequency | null; - external_relay_url: string | null; + email: string | null | undefined; + phone: string | null | undefined; + whatsapp: string | null | undefined; + website_url: string | null | undefined; + twitter_url: string | null | undefined; + facebook_url: string | null | undefined; + instagram_url: string | null | undefined; + threads_url: string | null | undefined; + youtube_url: string | null | undefined; + twitch_url: string | null | undefined; + tiktok_url: string | null | undefined; + spotify_url: string | null | undefined; + radiocut_url: string | null | undefined; + google_play_url: string | null | undefined; + app_store_url: string | null | undefined; + frequency: StationFrequency | null | undefined; + external_relay_url: string | null | undefined; user_metadata?: Metadata; system_metadata?: Metadata; }; diff --git a/defs/api/stations/[station]/files/[file]/metadata/PUT/Payload.ts b/defs/api/stations/[station]/files/[file]/metadata/PUT/Payload.ts index beccce16..46f47b01 100644 --- a/defs/api/stations/[station]/files/[file]/metadata/PUT/Payload.ts +++ b/defs/api/stations/[station]/files/[file]/metadata/PUT/Payload.ts @@ -1,12 +1,12 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type Payload = { - title?: string | null; - artist?: string | null; - album?: string | null; - album_artist?: string | null; - genre?: string | null; - year?: number | null; - comment?: string | null; - track?: number | null; + title?: string | null | undefined; + artist?: string | null | undefined; + album?: string | null | undefined; + album_artist?: string | null | undefined; + genre?: string | null | undefined; + year?: number | null | undefined; + comment?: string | null | undefined; + track?: number | null | undefined; }; diff --git a/defs/api/stations/[station]/files/suffle/POST/Output.schema.json b/defs/api/stations/[station]/files/shuffle/POST/Output.schema.json similarity index 100% rename from defs/api/stations/[station]/files/suffle/POST/Output.schema.json rename to defs/api/stations/[station]/files/shuffle/POST/Output.schema.json diff --git a/defs/api/stations/[station]/files/suffle/POST/Output.ts b/defs/api/stations/[station]/files/shuffle/POST/Output.ts similarity index 100% rename from defs/api/stations/[station]/files/suffle/POST/Output.ts rename to defs/api/stations/[station]/files/shuffle/POST/Output.ts diff --git a/defs/api/stations/[station]/files/unsuffle/POST/Output.schema.json b/defs/api/stations/[station]/files/unshuffle/POST/Output.schema.json similarity index 100% rename from defs/api/stations/[station]/files/unsuffle/POST/Output.schema.json rename to defs/api/stations/[station]/files/unshuffle/POST/Output.schema.json diff --git a/defs/api/stations/[station]/files/unsuffle/POST/Output.ts b/defs/api/stations/[station]/files/unshuffle/POST/Output.ts similarity index 100% rename from defs/api/stations/[station]/files/unsuffle/POST/Output.ts rename to defs/api/stations/[station]/files/unshuffle/POST/Output.ts diff --git a/defs/api/stations/[station]/now-playing/GET/Output.ts b/defs/api/stations/[station]/now-playing/GET/Output.ts index 53c94259..92a150b5 100644 --- a/defs/api/stations/[station]/now-playing/GET/Output.ts +++ b/defs/api/stations/[station]/now-playing/GET/Output.ts @@ -3,14 +3,17 @@ export type Output = | ({ kind: "none" } & { start_on_connect: boolean; - external_relay_url: string | null; - external_relay_error: string | null; + external_relay_url: string | null | undefined; + external_relay_error: string | null | undefined; + }) + | ({ kind: "live" } & { + title: string | null | undefined; + artist: string | null | undefined; }) - | ({ kind: "live" } & { title: string | null; artist: string | null }) | ({ kind: "external-relay" } & { url: string }) | ({ kind: "playlist" } & { file_id: string; filename: string; - title: string | null; - artist: string | null; + title: string | null | undefined; + artist: string | null | undefined; }); diff --git a/defs/api/users/POST/Payload.ts b/defs/api/users/POST/Payload.ts index 2fcccb8c..a0c77515 100644 --- a/defs/api/users/POST/Payload.ts +++ b/defs/api/users/POST/Payload.ts @@ -3,7 +3,7 @@ import type { Metadata } from "../../../db/Metadata.js"; export type Payload = { email: string; - phone: string | null; + phone: string | null | undefined; password: string; first_name: string; last_name: string; diff --git a/defs/api/users/[user]/PATCH/Payload.ts b/defs/api/users/[user]/PATCH/Payload.ts index d3bceebe..6589fec6 100644 --- a/defs/api/users/[user]/PATCH/Payload.ts +++ b/defs/api/users/[user]/PATCH/Payload.ts @@ -1,8 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type Payload = { - first_name: string | null; - last_name: string | null; - phone: string | null | null; - language: string | null | null; + first_name: string | null | undefined; + last_name: string | null | undefined; + phone: string | null | undefined | null | undefined; + language: string | null | undefined | null | undefined; }; diff --git a/defs/app-analytics/Analytics.ts b/defs/app-analytics/Analytics.ts index 974973b5..404a01ff 100644 --- a/defs/app-analytics/Analytics.ts +++ b/defs/app-analytics/Analytics.ts @@ -22,9 +22,9 @@ export type Analytics = { max_concurrent_listeners?: number; max_concurrent_listeners_date?: DateTime; by_day: Array>; - by_hour: Array> | null; - by_country: Array>; + by_hour: Array> | null | undefined; + by_country: Array>; by_station: Array>; - by_app_kind: Array>; + by_app_kind: Array>; by_app_version: Array>; }; diff --git a/defs/app-analytics/AppKindVersion.ts b/defs/app-analytics/AppKindVersion.ts index da63ee07..310810db 100644 --- a/defs/app-analytics/AppKindVersion.ts +++ b/defs/app-analytics/AppKindVersion.ts @@ -1,3 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AppKindVersion = { kind: string | null; version: number | null }; +export type AppKindVersion = { + kind: string | null | undefined; + version: number | null | undefined; +}; diff --git a/defs/db/Account.ts b/defs/db/Account.ts index 95d4611d..bda42761 100644 --- a/defs/db/Account.ts +++ b/defs/db/Account.ts @@ -6,12 +6,12 @@ import type { Metadata } from "./Metadata.js"; export type Account = { _id: string; plan_id: string; - payment_method_id: string | null; + payment_method_id: string | null | undefined; name: string; limits: AccountLimits; user_metadata: Metadata; system_metadata: Metadata; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/Admin.ts b/defs/db/Admin.ts index 87becf63..e2054354 100644 --- a/defs/db/Admin.ts +++ b/defs/db/Admin.ts @@ -8,9 +8,9 @@ export type Admin = { last_name: string; email: string; password: string; - language: string | null; + language: string | null | undefined; system_metadata: Metadata; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/AdminPublicUser.ts b/defs/db/AdminPublicUser.ts index f1e8b45a..9310cae9 100644 --- a/defs/db/AdminPublicUser.ts +++ b/defs/db/AdminPublicUser.ts @@ -7,11 +7,11 @@ export type AdminPublicUser = { first_name: string; last_name: string; email: string; - phone: string | null; - language: string | null; + phone: string | null | undefined; + language: string | null | undefined; user_metadata: Metadata; system_metadata: Metadata; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/AudioFile.ts b/defs/db/AudioFile.ts index daa69122..0be81d5f 100644 --- a/defs/db/AudioFile.ts +++ b/defs/db/AudioFile.ts @@ -5,7 +5,7 @@ import type { DateTime } from "../DateTime.js"; export type AudioFile = { _id: string; station_id: string; - sha_256: string; + sha256: string; len: number; duration_ms: number; bytes_sec: number; diff --git a/defs/db/AudioMetadata.ts b/defs/db/AudioMetadata.ts index 3bfc906b..d0636398 100644 --- a/defs/db/AudioMetadata.ts +++ b/defs/db/AudioMetadata.ts @@ -1,12 +1,12 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type AudioMetadata = { - title: string | null; - artist: string | null; - album: string | null; - album_artist: string | null; - genre: string | null; - year: number | null; - comment: string | null; - track: number | null; + title: string | null | undefined; + artist: string | null | undefined; + album: string | null | undefined; + album_artist: string | null | undefined; + genre: string | null | undefined; + year: number | null | undefined; + comment: string | null | undefined; + track: number | null | undefined; }; diff --git a/defs/db/BaseAccessToken.ts b/defs/db/BaseAccessToken.ts index 7aae359e..209addef 100644 --- a/defs/db/BaseAccessToken.ts +++ b/defs/db/BaseAccessToken.ts @@ -8,10 +8,10 @@ export type BaseAccessToken = _id: string; hash: string; media_hash: string; - last_used_at: DateTime | null; + last_used_at: DateTime | null | undefined; hits: number; created_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; } & AccessTokenScope & AccessTokenGeneratedBy; diff --git a/defs/db/Deployment.ts b/defs/db/Deployment.ts index aa38eb60..44f4e452 100644 --- a/defs/db/Deployment.ts +++ b/defs/db/Deployment.ts @@ -12,6 +12,6 @@ export type Deployment = { state: DeploymentState; created_at: DateTime; updated_at: DateTime; - health_checked_at: DateTime | null; - dropped_at: DateTime | null; + health_checked_at: DateTime | null | undefined; + dropped_at: DateTime | null | undefined; }; diff --git a/defs/db/EmailVerificationCode.ts b/defs/db/EmailVerificationCode.ts index aa05e889..0df8f86f 100644 --- a/defs/db/EmailVerificationCode.ts +++ b/defs/db/EmailVerificationCode.ts @@ -5,6 +5,6 @@ export type EmailVerificationCode = { _id: string; email: string; hash: string; - used_at: DateTime | null; + used_at: DateTime | null | undefined; created_at: DateTime; }; diff --git a/defs/db/MediaSession.ts b/defs/db/MediaSession.ts index adb597a5..209ed4a1 100644 --- a/defs/db/MediaSession.ts +++ b/defs/db/MediaSession.ts @@ -9,11 +9,11 @@ export type MediaSession = { station_id: string; deployment_id: string; state: MediaSessionState; - now_playing: MediaSessionNowPlaying | null; + now_playing: MediaSessionNowPlaying | null | undefined; transfer_bytes: number; - closed_at: DateTime | null; - duration_ms: number | null; - health_checked_at: DateTime | null; + closed_at: DateTime | null | undefined; + duration_ms: number | null | undefined; + health_checked_at: DateTime | null | undefined; created_at: DateTime; updated_at: DateTime; } & MediaSessionKind; diff --git a/defs/db/MediaSessionKind.ts b/defs/db/MediaSessionKind.ts index 09f3e555..4724e18d 100644 --- a/defs/db/MediaSessionKind.ts +++ b/defs/db/MediaSessionKind.ts @@ -4,7 +4,7 @@ import type { Request } from "./http/Request.js"; export type MediaSessionKind = | ({ kind: "playlist" } & { - resumed_from: string | null; + resumed_from: string | null | undefined; last_audio_file_id: string; last_audio_file_order: number; last_audio_chunk_i: number; diff --git a/defs/db/MediaSessionNowPlaying.ts b/defs/db/MediaSessionNowPlaying.ts index e23f0788..0222bb94 100644 --- a/defs/db/MediaSessionNowPlaying.ts +++ b/defs/db/MediaSessionNowPlaying.ts @@ -1,3 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type MediaSessionNowPlaying = { title: string; artist: string | null }; +export type MediaSessionNowPlaying = { + title: string; + artist: string | null | undefined; +}; diff --git a/defs/db/OwnerDeploymentInfo.ts b/defs/db/OwnerDeploymentInfo.ts index c2d4ac8a..96699f91 100644 --- a/defs/db/OwnerDeploymentInfo.ts +++ b/defs/db/OwnerDeploymentInfo.ts @@ -5,5 +5,5 @@ export type OwnerDeploymentInfo = { deployment_id: string; task_id: string; content_type: string; - health_checked_at: DateTime | null; + health_checked_at: DateTime | null | undefined; }; diff --git a/defs/db/PaymentMethod.ts b/defs/db/PaymentMethod.ts index f0848767..67cce0ed 100644 --- a/defs/db/PaymentMethod.ts +++ b/defs/db/PaymentMethod.ts @@ -7,5 +7,5 @@ export type PaymentMethod = { user_id: string; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; } & PaymentMethodKind; diff --git a/defs/db/PaymentMethodKind.ts b/defs/db/PaymentMethodKind.ts index 7707ad3a..ceed0a73 100644 --- a/defs/db/PaymentMethodKind.ts +++ b/defs/db/PaymentMethodKind.ts @@ -4,6 +4,6 @@ export type PaymentMethodKind = { kind: "card" } & { token: string; card_type: string; last_4: string; - expiration_year: string | null; - expiration_month: string | null; + expiration_year: string | null | undefined; + expiration_month: string | null | undefined; }; diff --git a/defs/db/Plan.ts b/defs/db/Plan.ts index 230e8df7..9998cbf3 100644 --- a/defs/db/Plan.ts +++ b/defs/db/Plan.ts @@ -14,5 +14,5 @@ export type Plan = { is_user_selectable: boolean; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/PlayHistoryItem.ts b/defs/db/PlayHistoryItem.ts index ffadfdc3..2902038a 100644 --- a/defs/db/PlayHistoryItem.ts +++ b/defs/db/PlayHistoryItem.ts @@ -7,6 +7,6 @@ export type PlayHistoryItem = { station_id: string; deployment_id: string; title: string; - artist: string | null; + artist: string | null | undefined; created_at: DateTime; } & PlayHistoryItemKind; diff --git a/defs/db/Probe.ts b/defs/db/Probe.ts index c9a8247d..92edfe19 100644 --- a/defs/db/Probe.ts +++ b/defs/db/Probe.ts @@ -9,5 +9,5 @@ export type Probe = { duration_ms: number; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; } & ProbeResult; diff --git a/defs/db/ProbeResult.ts b/defs/db/ProbeResult.ts index e0c65a91..9cd01dfd 100644 --- a/defs/db/ProbeResult.ts +++ b/defs/db/ProbeResult.ts @@ -3,9 +3,9 @@ export type ProbeResult = | ({ r: "ok" } & { document: Record }) | ({ r: "error" } & { - error_exit_code: number | null; - error_stdout: string | null; - error_stderr: string | null; + error_exit_code: number | null | undefined; + error_stdout: string | null | undefined; + error_stderr: string | null | undefined; error_display: string; error_debug: string; }); diff --git a/defs/db/RelaySession.ts b/defs/db/RelaySession.ts index d1312dcb..bc449f22 100644 --- a/defs/db/RelaySession.ts +++ b/defs/db/RelaySession.ts @@ -9,8 +9,8 @@ export type RelaySession = { target_deployment_id: string; state: RelaySessionState; transfer_bytes: number; - closed_at: DateTime | null; - duration_ms: number | null; + closed_at: DateTime | null | undefined; + duration_ms: number | null | undefined; created_at: DateTime; updated_at: DateTime; }; diff --git a/defs/db/SentEmailAddress.ts b/defs/db/SentEmailAddress.ts index e86191f7..a811510f 100644 --- a/defs/db/SentEmailAddress.ts +++ b/defs/db/SentEmailAddress.ts @@ -1,3 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type SentEmailAddress = { name: string | null; email: string }; +export type SentEmailAddress = { + name: string | null | undefined; + email: string; +}; diff --git a/defs/db/SentEmailBase.ts b/defs/db/SentEmailBase.ts index 8507a4cf..416d5c01 100644 --- a/defs/db/SentEmailBase.ts +++ b/defs/db/SentEmailBase.ts @@ -10,6 +10,6 @@ export type SentEmailBase = { subject: string; text: string; html: string; - reply_to: SentEmailAddress | null; + reply_to: SentEmailAddress | null | undefined; created_at: DateTime; } & SentEmailKind; diff --git a/defs/db/SentEmailKind.ts b/defs/db/SentEmailKind.ts index 656ee364..0204fc0b 100644 --- a/defs/db/SentEmailKind.ts +++ b/defs/db/SentEmailKind.ts @@ -11,7 +11,7 @@ export type SentEmailKind = { data: { receiver_email: string; invitation_id: string; - user_sender_id: string | null; - admin_sender_id: string | null; + user_sender_id: string | null | undefined; + admin_sender_id: string | null | undefined; }; }; diff --git a/defs/db/Station.ts b/defs/db/Station.ts index d240cab6..344c4095 100644 --- a/defs/db/Station.ts +++ b/defs/db/Station.ts @@ -13,36 +13,36 @@ export type Station = { picture_id: string; name: string; slug: string; - slogan: string | null; + slogan: string | null | undefined; type_of_content: StationTypeOfContent; country_code: CountryCode; lang_code: LangCode; - description: string | null; - frequency: StationFrequency | null; - email: string | null; - phone: string | null; - whatsapp: string | null; - website_url: string | null; - twitter_url: string | null; - facebook_url: string | null; - instagram_url: string | null; - threads_url: string | null; - youtube_url: string | null; - twitch_url: string | null; - tiktok_url: string | null; - spotify_url: string | null; - radiocut_url: string | null; - google_play_url: string | null; - app_store_url: string | null; + description: string | null | undefined; + frequency: StationFrequency | null | undefined; + email: string | null | undefined; + phone: string | null | undefined; + whatsapp: string | null | undefined; + website_url: string | null | undefined; + twitter_url: string | null | undefined; + facebook_url: string | null | undefined; + instagram_url: string | null | undefined; + threads_url: string | null | undefined; + youtube_url: string | null | undefined; + twitch_url: string | null | undefined; + tiktok_url: string | null | undefined; + spotify_url: string | null | undefined; + radiocut_url: string | null | undefined; + google_play_url: string | null | undefined; + app_store_url: string | null | undefined; user_metadata: Metadata; system_metadata: Metadata; - external_relay_url: string | null; + external_relay_url: string | null | undefined; external_relay_redirect: boolean; source_password: string; - owner_deployment_info: OwnerDeploymentInfo | null; - last_external_relay_probe_started_at: DateTime | null; + owner_deployment_info: OwnerDeploymentInfo | null | undefined; + last_external_relay_probe_started_at: DateTime | null | undefined; playlist_is_randomly_shuffled: boolean; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/StreamConnection.ts b/defs/db/StreamConnection.ts index ad07a696..e8b37520 100644 --- a/defs/db/StreamConnection.ts +++ b/defs/db/StreamConnection.ts @@ -7,15 +7,15 @@ export type StreamConnection = { _id: string; station_id: string; deployment_id: string; - transfer_bytes: number | null; - duration_ms: number | null; + transfer_bytes: number | null | undefined; + duration_ms: number | null | undefined; is_open: boolean; created_at: DateTime; - country_code: CountryCode | null; + country_code: CountryCode | null | undefined; ip: string; is_external_relay_redirect: boolean; _manually_closed: boolean; request: Request; last_transfer_at: DateTime; - closed_at: DateTime | null; + closed_at: DateTime | null | undefined; }; diff --git a/defs/db/StreamConnectionLite.ts b/defs/db/StreamConnectionLite.ts index 42d929a7..8acaf68d 100644 --- a/defs/db/StreamConnectionLite.ts +++ b/defs/db/StreamConnectionLite.ts @@ -7,14 +7,14 @@ export type StreamConnectionLite = { st: string; op: boolean; ip: string; - cc: CountryCode | null; - du: number | null; - by: number | null; - br: string | null; - do: string | null; - os: string | null; + cc: CountryCode | null | undefined; + du: number | null | undefined; + by: number | null | undefined; + br: string | null | undefined; + do: string | null | undefined; + os: string | null | undefined; ca: DateTime; re: boolean; _m: boolean; - cl: DateTime | null; + cl: DateTime | null | undefined; }; diff --git a/defs/db/TokenUserRecovery.ts b/defs/db/TokenUserRecovery.ts index 046d2788..369f8026 100644 --- a/defs/db/TokenUserRecovery.ts +++ b/defs/db/TokenUserRecovery.ts @@ -5,6 +5,6 @@ export type TokenUserRecovery = { _id: string; hash: string; user_id: string; - used_at: DateTime | null; + used_at: DateTime | null | undefined; created_at: DateTime; }; diff --git a/defs/db/User.ts b/defs/db/User.ts index f44d7d91..6996acff 100644 --- a/defs/db/User.ts +++ b/defs/db/User.ts @@ -7,12 +7,12 @@ export type User = { first_name: string; last_name: string; email: string; - phone: string | null; - language: string | null; - password: string | null; + phone: string | null | undefined; + language: string | null | undefined; + password: string | null | undefined; user_metadata: Metadata; system_metadata: Metadata; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/UserPublicUser.ts b/defs/db/UserPublicUser.ts index 2ba64e8a..52ccc60c 100644 --- a/defs/db/UserPublicUser.ts +++ b/defs/db/UserPublicUser.ts @@ -7,10 +7,10 @@ export type UserPublicUser = { first_name: string; last_name: string; email: string; - phone: string | null; - language: string | null; + phone: string | null | undefined; + language: string | null | undefined; user_metadata: Metadata; created_at: DateTime; updated_at: DateTime; - deleted_at: DateTime | null; + deleted_at: DateTime | null | undefined; }; diff --git a/defs/db/WsStatsConnection.ts b/defs/db/WsStatsConnection.ts index 03d9d33c..1e73bfa6 100644 --- a/defs/db/WsStatsConnection.ts +++ b/defs/db/WsStatsConnection.ts @@ -6,13 +6,13 @@ export type WsStatsConnection = { _id: string; st: string; dp: string; - du: number | null; + du: number | null | undefined; op: boolean; - cc: CountryCode | null; + cc: CountryCode | null | undefined; ip: string; - ap: string | null; - av: number | null; + ap: string | null | undefined; + av: number | null | undefined; re: number; ca: DateTime; - cl: DateTime | null; + cl: DateTime | null | undefined; }; diff --git a/defs/db/http/Request.ts b/defs/db/http/Request.ts index 99b001bd..c4ef6fc2 100644 --- a/defs/db/http/Request.ts +++ b/defs/db/http/Request.ts @@ -9,7 +9,7 @@ import type { Version } from "./Version.js"; export type Request = { real_ip: string; - country_code: CountryCode | null; + country_code: CountryCode | null | undefined; local_addr: SocketAddr; remote_addr: SocketAddr; version: Version; diff --git a/defs/db/http/Uri.ts b/defs/db/http/Uri.ts index 3b6a431f..1ace722d 100644 --- a/defs/db/http/Uri.ts +++ b/defs/db/http/Uri.ts @@ -2,9 +2,9 @@ export type Uri = { uri: string; - scheme: string | null; - host: string | null; - port: number | null; + scheme: string | null | undefined; + host: string | null | undefined; + port: number | null | undefined; path: string; - query: string | null; + query: string | null | undefined; }; diff --git a/defs/ops/AdminPatch.ts b/defs/ops/AdminPatch.ts index 6d403020..0cc8c943 100644 --- a/defs/ops/AdminPatch.ts +++ b/defs/ops/AdminPatch.ts @@ -4,6 +4,6 @@ import type { Metadata } from "../db/Metadata.js"; export type AdminPatch = { first_name?: string; last_name?: string; - language?: string | null; + language?: string | null | undefined; system_metadata?: Metadata; }; diff --git a/defs/ops/StationPatch.ts b/defs/ops/StationPatch.ts index 7f85d362..dd9f1f70 100644 --- a/defs/ops/StationPatch.ts +++ b/defs/ops/StationPatch.ts @@ -8,28 +8,28 @@ import type { StationTypeOfContent } from "../db/StationTypeOfContent.js"; export type StationPatch = { name?: string; picture_id?: string; - slogan?: string | null; - description?: string | null; + slogan?: string | null | undefined; + description?: string | null | undefined; type_of_content?: StationTypeOfContent; country_code?: CountryCode; lang_code?: LangCode; - frequency?: StationFrequency | null; - email?: string | null; - phone?: string | null; - whatsapp?: string | null; - website_url?: string | null; - twitter_url?: string | null; - facebook_url?: string | null; - instagram_url?: string | null; - threads_url?: string | null; - youtube_url?: string | null; - twitch_url?: string | null; - tiktok_url?: string | null; - spotify_url?: string | null; - radiocut_url?: string | null; - google_play_url?: string | null; - app_store_url?: string | null; - external_relay_url?: string | null; + frequency?: StationFrequency | null | undefined; + email?: string | null | undefined; + phone?: string | null | undefined; + whatsapp?: string | null | undefined; + website_url?: string | null | undefined; + twitter_url?: string | null | undefined; + facebook_url?: string | null | undefined; + instagram_url?: string | null | undefined; + threads_url?: string | null | undefined; + youtube_url?: string | null | undefined; + twitch_url?: string | null | undefined; + tiktok_url?: string | null | undefined; + spotify_url?: string | null | undefined; + radiocut_url?: string | null | undefined; + google_play_url?: string | null | undefined; + app_store_url?: string | null | undefined; + external_relay_url?: string | null | undefined; external_relay_redirect?: boolean; user_metadata?: Metadata; system_metadata?: Metadata; diff --git a/defs/payments/api/PaymentsErrorKind.ts b/defs/payments/api/PaymentsErrorKind.ts index 1edeeede..5975df9b 100644 --- a/defs/payments/api/PaymentsErrorKind.ts +++ b/defs/payments/api/PaymentsErrorKind.ts @@ -1,7 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type PaymentsErrorKind = - | ({ kind: "provider" } & { provider_error_type: string | null }) + | ({ kind: "provider" } & { provider_error_type: string | null | undefined }) | { kind: "payload" } | { kind: "access-token-not-present" } | { kind: "access-token-mismatch" } diff --git a/defs/payments/api/save-payment-method/Response.ts b/defs/payments/api/save-payment-method/Response.ts index 322784db..650172ee 100644 --- a/defs/payments/api/save-payment-method/Response.ts +++ b/defs/payments/api/save-payment-method/Response.ts @@ -2,8 +2,8 @@ export type Response = { card_type: string; - expiration_month: string | null; - expiration_year: string | null; + expiration_month: string | null | undefined; + expiration_year: string | null | undefined; last_4: string; payment_method_token: string; }; diff --git a/front/admin/package-lock.json b/front/admin/package-lock.json index 6e914aab..9746a367 100644 --- a/front/admin/package-lock.json +++ b/front/admin/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "app", "version": "0.0.1", + "dependencies": { + "openapi-fetch": "^0.9.2" + }, "devDependencies": { "@mdi/js": "^7.0.96", "@playwright/test": "1.25.0", @@ -3265,6 +3268,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-fetch": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.2.tgz", + "integrity": "sha512-EEzVCoGAbGP1Z3eTZXI0urHtvb+JRKLnPbnemsUzeTWcHCaAXsdhAWYfdLAx4fTNNUaL23BQLup8dQjMMkCRqA==", + "dependencies": { + "openapi-typescript-helpers": "^0.0.7" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.7.tgz", + "integrity": "sha512-7nwlAtdA1fULipibFRBWE/rnF114q6ejRYzNvhdA/x+qTWAZhXGLc/368dlwMlyJDvCQMCnADjpzb5BS5ZmNSA==" + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -7026,6 +7042,19 @@ "mimic-fn": "^2.1.0" } }, + "openapi-fetch": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.2.tgz", + "integrity": "sha512-EEzVCoGAbGP1Z3eTZXI0urHtvb+JRKLnPbnemsUzeTWcHCaAXsdhAWYfdLAx4fTNNUaL23BQLup8dQjMMkCRqA==", + "requires": { + "openapi-typescript-helpers": "^0.0.7" + } + }, + "openapi-typescript-helpers": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.7.tgz", + "integrity": "sha512-7nwlAtdA1fULipibFRBWE/rnF114q6ejRYzNvhdA/x+qTWAZhXGLc/368dlwMlyJDvCQMCnADjpzb5BS5ZmNSA==" + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", diff --git a/front/admin/package.json b/front/admin/package.json index c8eafc3f..a55b9326 100644 --- a/front/admin/package.json +++ b/front/admin/package.json @@ -45,7 +45,8 @@ "workbox-cacheable-response": "^6.5.4", "workbox-precaching": "^6.5.4", "workbox-routing": "^6.5.4", - "workbox-strategies": "^6.5.4" + "workbox-strategies": "^6.5.4", + "openapi-fetch": "^0.9.2" }, "type": "module" } diff --git a/front/admin/src/hooks.server.ts b/front/admin/src/hooks.server.ts index 0ceede74..0275f27a 100644 --- a/front/admin/src/hooks.server.ts +++ b/front/admin/src/hooks.server.ts @@ -29,14 +29,14 @@ export const handle: Handle = async ({ event, resolve }) => { event.locals.protocol = proto; - let buffer = ""; + let replaced = false; const res = await resolve(event, { transformPageChunk: ({ html, done }) => { - buffer += html; - if(done) { + if(!replaced && html.includes("%html_attrs%")) { + replaced = true; const lang = event.locals.lang || "en"; const dir = event.locals.dir || "ltr"; - return buffer.replace("%html_attrs%", `lang="${lang}" dir="${dir}"`); + return html.replace("%html_attrs%", `lang="${lang}" dir="${dir}"`); } else { return undefined; } @@ -62,10 +62,10 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { server_logger.debug(`handle-fetch: ${event.request.url} => ${request.url} ip=${event.locals.ip} proto=${event.locals.protocol}`) - const target = new Request(request) + const headers = new Headers(); const host = event.request.headers.get("host"); - if(host) target.headers.set("x-host", host); + if(host) headers.set("x-host", host); for(const key of [ "x-forwarded-proto", @@ -76,23 +76,18 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { "cookie" ]) { const v = event.request.headers.get(key); - if(v) target.headers.set(key, v); + if(v) headers.set(key, v); } - target.headers.set(FORWARD_IP_HEADER, event.locals.ip); - target.headers.set(PROTOCOL_HEADER, event.locals.protocol); - target.headers.set("x-kit-url", event.request.url); + headers.set(FORWARD_IP_HEADER, event.locals.ip); + headers.set(PROTOCOL_HEADER, event.locals.protocol); + headers.set("x-kit-url", event.request.url); - // const src_cookies = (event.request.headers.get("cookie")?.split(";") || []).map(s => s.trim()); - // const cookie = [...new Set([...src_cookies, ...event.locals.cookie])].join("; ").trim(); - // if(cookie) target.headers.set("cookie", cookie); - try { const res = await fetch(url, { - method: target.method, - headers: target.headers, - body: target.body, - // mode: "same-origin" + method: request.method, + headers, + body: request.body, }); const lang = res.headers.get(LOCALE_LANG_HEADER); @@ -101,13 +96,6 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { const dir = res.headers.get(LOCALE_DIR_HEADER); if(dir != null) event.locals.dir = dir; - // const set_cookie = res.headers.get("set-cookie"); - // if(set_cookie) { - // event.locals.set_cookie.add(set_cookie); - // const cookie = set_cookie.split(";")[0]; - // if(cookie) event.locals.cookie.add(cookie); - // } - return res; } catch(e: any) { diff --git a/front/admin/src/lib/client.ts b/front/admin/src/lib/client.ts new file mode 100644 index 00000000..d2018523 --- /dev/null +++ b/front/admin/src/lib/client.ts @@ -0,0 +1,17 @@ +import type { paths } from "../../../../openapi"; +import createClient from "openapi-fetch"; + +export const { GET, POST, PUT, PATCH, DELETE } = createClient({ + baseUrl: "/api/" +}) + +export type Unwrap = R extends { data: infer T } ? Exclude : never; + +export const unwrap = ( + result: + | { data: T, error?: undefined } + | { data?: undefined, error: { error: { status: number, code: string, message: string } } } +): NonNullable => { + if (result.error) throw new Error(result.error.error.message) + return result.data!; +} \ No newline at end of file diff --git a/front/admin/src/lib/css/app.css b/front/admin/src/lib/css/app.css index 51d98b02..2fc91772 100644 --- a/front/admin/src/lib/css/app.css +++ b/front/admin/src/lib/css/app.css @@ -98,7 +98,7 @@ button, input[type="submit"] { padding: 0; border: 0; background: transparent; - appareance: none; + appearance: none; cursor: pointer; } diff --git a/front/admin/src/lib/load.ts b/front/admin/src/lib/load.ts index f2e31010..095ff666 100644 --- a/front/admin/src/lib/load.ts +++ b/front/admin/src/lib/load.ts @@ -3,6 +3,12 @@ import { ApiError } from "$server/error"; import { error, redirect, type LoadEvent, type NumericRange } from "@sveltejs/kit"; import StatusCode from "http-status-codes"; +import type { paths } from "../../../../openapi"; +import createClient from "openapi-fetch"; +export const client = createClient({ + baseUrl: browser ? "/api/" : "https://internal.test/api/", +}); + export const load_get_me = async ( { fetch, url }: Pick ): Promise<(import("$server/defs/PublicAdmin").PublicAdmin & { media_key: string }) | null> => { @@ -52,6 +58,34 @@ export const load_get = async ( return body as T } +export const load_call = async ( + fn: () => Promise< + | { data: T, error?: undefined } + | { data?: undefined, error: { error: { status: number, code: import("$defs/error/PublicErrorCode").PublicErrorCode, message: string } } } + >, + { redirectToLoginOnAuthErrors = true } = {} +): Promise> => { + try { + const result = await fn(); + if(result.error === undefined) { + return result.data!; + } else { + if(result.error?.error?.status === StatusCode.UNAUTHORIZED && redirectToLoginOnAuthErrors) { + const to = `${location.pathname}${location.search}`; + const login_url = to === "/" ? "/login" : `/login#${to}` + redirect(302, login_url); + } else { + const api_error = ApiError.from_error_payload(result.error); + error(api_error.status as NumericRange<400, 599>, api_error.toJSON().error); + } + } + } catch(e: any) { + console.log(e); + const api_error = new ApiError(502, "FRONT_GATEWAY_FETCH", `Bad gateway (fetch)`); + error(api_error.status as NumericRange<400, 599>, api_error.toJSON().error); + } +} + export const not_found_load = () => { error(404, { status: 404, message: "Page not found", code: "CLIENT_PAGE_NOT_FOUND" }); } \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/+layout.server.ts b/front/admin/src/routes/(online)/(app)/+layout.server.ts new file mode 100644 index 00000000..9688569d --- /dev/null +++ b/front/admin/src/routes/(online)/(app)/+layout.server.ts @@ -0,0 +1,60 @@ +import { client, load_call } from "$lib/load"; +import { redirect } from "@sveltejs/kit"; + +const unpage = (page: { items: T[] }): [all: T[], current: T[], deleted: T[]] => { + const all = page.items; + const current: T[] = []; + const deleted: T[] = []; + for (const item of all) { + if (item.deleted_at == null) current.push(item); + else deleted.push(item); + } + + return [all, current, deleted]; +} + +export const load = (async ({ fetch, url, parent, depends }) => { + + depends("resource:plans", "resource:admins", "resource:users", "resource:accounts", "resource:stations"); + depends("api:admins", "api:users", "api:accounts", "api:stations"); + + const { maybe_admin } = await parent(); + if (maybe_admin == null) { + const to = `${url.pathname}${url.search}`; + const login_url = to === "/" ? "/login" : `/login#${to}` + redirect(302, login_url); + } + + const params = { query: { show: "all" as const, limit: 100_000 } }; + + const [ + admins_page, + users_page, + accounts_page, + stations_page, + plans_page, + ] = await Promise.all([ + load_call(() => client.GET("/admins", { params, fetch })), + load_call(() => client.GET("/users", { params, fetch })), + load_call(() => client.GET("/accounts", { params, fetch })), + load_call(() => client.GET("/stations", { params, fetch })), + load_call(() => client.GET("/plans", { params, fetch })), + ]); + + + const [all_admins, admins, deleted_admins] = unpage(admins_page); + const [all_users, users, deleted_users] = unpage(users_page); + const [all_accounts, accounts, deleted_accounts] = unpage(accounts_page); + const [all_stations, stations, deleted_stations] = unpage(stations_page); + const [all_plans, plans, deleted_plans] = unpage(plans_page); + + return { + all_admins, admins, deleted_admins, + all_users, users, deleted_users, + all_accounts, accounts, deleted_accounts, + all_stations, stations, deleted_stations, + all_plans, plans, deleted_plans, + admin: maybe_admin, + } + +}) satisfies import("./$types").LayoutServerLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/+layout.ts b/front/admin/src/routes/(online)/(app)/+layout.ts deleted file mode 100644 index 63f76d6d..00000000 --- a/front/admin/src/routes/(online)/(app)/+layout.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { load_get } from "$lib/load"; -import type { Paged } from "$server/defs/Paged"; -import { qss } from "$share/qs"; -import { redirect } from "@sveltejs/kit"; - -const unpage = (page: Paged): [all: T[], current: T[], deleted: T[]] => { - const all = page.items; - const current: T[] = []; - const deleted: T[] = []; - for(const item of all) { - if(item.deleted_at == null) current.push(item); - else deleted.push(item); - } - - return [all, current, deleted]; -} - -export const load = (async ({ fetch, url, parent, depends }) => { - - depends("resource:plans", "resource:admins", "resource:users", "resource:accounts", "resource:stations"); - depends("api:admins", "api:users","api:accounts", "api:stations"); - - const { maybe_admin } = await parent(); - if (maybe_admin == null) { - const to = `${url.pathname}${url.search}`; - const login_url = to === "/" ? "/login" : `/login#${to}` - redirect(302, login_url); - } - - const [ - admins_page, - users_page, - accounts_page, - stations_page, - plans_page, - ] = await Promise.all([ - load_get( - `/api/admins${qss({ show: "all", limit: 100_000 })}`, - { fetch, url } - ), - - load_get( - `/api/users${qss({ show: "all", limit: 100_000 })}`, - { fetch, url } - ), - - load_get( - `/api/accounts${qss({ show: "all", limit: 100_000 })}`, - { fetch, url } - ), - - load_get( - `/api/stations${qss({ show: "all", limit: 100_000 })}`, - { fetch, url } - ), - - load_get( - // TODO: add pagination - `/api/plans${qss({ show: "all" })}`, - { fetch, url } - ), - ]); - - - const [ all_admins, admins, deleted_admins ] = unpage(admins_page); - const [ all_users, users, deleted_users ] = unpage(users_page); - const [ all_accounts, accounts, deleted_accounts ] = unpage(accounts_page); - const [ all_stations, stations, deleted_stations ] = unpage(stations_page); - const [ all_plans, plans, deleted_plans ] = unpage(plans_page); - - return { - all_admins, admins, deleted_admins, - all_users, users, deleted_users, - all_accounts, accounts, deleted_accounts, - all_stations, stations, deleted_stations, - all_plans, plans, deleted_plans, - admin: maybe_admin, - } - -}) satisfies import("./$types").LayoutLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/+page.server.ts b/front/admin/src/routes/(online)/(app)/+page.server.ts new file mode 100644 index 00000000..0ecf16b1 --- /dev/null +++ b/front/admin/src/routes/(online)/(app)/+page.server.ts @@ -0,0 +1,6 @@ +import { client, load_call } from "$lib/load"; + +export const load = (async ({ fetch }) => { + const { stats } = await load_call(() => client.GET("/stream-stats", { fetch })); + return { stats } +}) satisfies import("./$types").PageServerLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/+page.ts b/front/admin/src/routes/(online)/(app)/+page.ts deleted file mode 100644 index 43f76996..00000000 --- a/front/admin/src/routes/(online)/(app)/+page.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { load_get } from "$lib/load"; - -export const load = (async ({ fetch, url }) => { - - const { stats } = await load_get("/api/stream-stats", { fetch, url }); - - return { stats } - -}) satisfies import("./$types").PageLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.ts b/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.server.ts similarity index 58% rename from front/admin/src/routes/(online)/(app)/accounts/[account]/+page.ts rename to front/admin/src/routes/(online)/(app)/accounts/[account]/+page.server.ts index 3728c5a4..ca423b6a 100644 --- a/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.ts +++ b/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.server.ts @@ -1,7 +1,7 @@ -import { load_get } from "$lib/load"; +import { client, load_call } from "$lib/load"; import { error } from "@sveltejs/kit"; -export const load = (async ({ parent, params, fetch, url }) => { +export const load = (async ({ parent, params, fetch }) => { const { stations, all_accounts, all_plans } = await parent(); @@ -19,10 +19,10 @@ export const load = (async ({ parent, params, fetch, url }) => { { members }, { stats } ] = await Promise.all([ - load_get(`/api/accounts/${account._id}/members`, { fetch, url }), - load_get(`/api/accounts/${params.account}/stream-stats`, { fetch, url }), + load_call(() => client.GET("/accounts/{account}/members", { params: { path: { account: account._id } }, fetch })), + load_call(() => client.GET("/accounts/{account}/stream-stats", { params: { path: { account: account._id } }, fetch })), ]); return { account, plan, members, stats, account_stations } -}) satisfies import("./$types").PageLoad; +}) satisfies import("./$types").PageServerLoad; diff --git a/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.svelte b/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.svelte index fd077b2f..9e8bd281 100644 --- a/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/accounts/[account]/+page.svelte @@ -17,6 +17,7 @@ import { goto } from "$app/navigation"; import PageMenuItem from "$lib/components/PageMenu/PageMenuItem.svelte"; import { STATION_PICTURES_VERSION } from "$defs/constants"; + import { DELETE, PATCH, unwrap } from "$lib/client"; const date = (d: string | Date) => { const date = new Date(d); @@ -40,11 +41,16 @@ changing_plan = true; try { - let payload: import("$api/accounts/[account]/PATCH/Payload").Payload = { - plan_id: selected_plan._id, - }; - - await _patch(`/api/accounts/${data.account._id}`, payload); + unwrap(await PATCH("/accounts/{account}", { + params: { + path: { + account: data.account._id, + } + }, + body: { + plan_id: selected_plan._id, + } + })); _message("Account plan updated"); invalidateAll(); selected_plan = null; @@ -67,7 +73,13 @@ if(deleting) return; deleting = true; try { - await _delete(`/api/accounts/${data.account._id}`); + unwrap(await DELETE("/accounts/{account}", { + params: { + path: { + account: data.account._id, + } + } + })); delete_open = false; _message("Account deleted"); await goto("/accounts", { invalidateAll: true }); diff --git a/front/admin/src/routes/(online)/(app)/accounts/[account]/limits.svelte b/front/admin/src/routes/(online)/(app)/accounts/[account]/limits.svelte index dd27351a..a956eb75 100644 --- a/front/admin/src/routes/(online)/(app)/accounts/[account]/limits.svelte +++ b/front/admin/src/routes/(online)/(app)/accounts/[account]/limits.svelte @@ -6,6 +6,7 @@ import { sleep } from "$share/util"; import type { AccountLimits } from "$server/defs/AccountLimits"; import CircularMeter from "$lib/components/CircularMeter/CircularMeter.svelte"; + import { GET, unwrap } from "$lib/client"; const units = [ "B", "KB", "MB", "GB", "TB" ]; @@ -53,7 +54,7 @@ } if(Date.now() - last < LIMITS_UPDATE_INTERVAL) continue; try { - const limits: AccountLimits = await _get(`/api/accounts/${data.account._id}/limits`); + const { account: { limits } } = unwrap(await GET("/accounts/{account}", { params: { path: { account: data.account._id } } })); logger.info(`account limits updated`); data.account.limits = limits; } catch(e) { diff --git a/front/admin/src/routes/(online)/(app)/analytics/+page.svelte b/front/admin/src/routes/(online)/(app)/analytics/+page.svelte index 4a3ac3d5..26fb1f1f 100644 --- a/front/admin/src/routes/(online)/(app)/analytics/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/analytics/+page.svelte @@ -65,7 +65,6 @@ import type { CountryCode } from "$server/defs/CountryCode"; import type { QueryKind, StationItem } from "$share/analytics/AnalyticsFilters.svelte"; import type { Data } from "$share/analytics/AnalyticsData.svelte"; - import { ripple } from "$share/ripple"; diff --git a/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts b/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts new file mode 100644 index 00000000..76ffc66a --- /dev/null +++ b/front/admin/src/routes/(online)/(app)/listeners/+page.server.ts @@ -0,0 +1,21 @@ +import { load_call, client } from "$lib/load"; + +export const load = (async ({ fetch }) => { + const stream_connections = await load_call( + () => client.GET("/stream-connections", { + params: { + query: { + show: "open", + limit: 100_000, + sort: "creation-desc" + } + }, + fetch + }) + ) + + return { + stream_connections + } + +}) satisfies import("./$types").PageServerLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/listeners/+page.svelte b/front/admin/src/routes/(online)/(app)/listeners/+page.svelte index 765f0603..684b1833 100644 --- a/front/admin/src/routes/(online)/(app)/listeners/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/listeners/+page.svelte @@ -13,6 +13,7 @@ import { _get } from "$share/net.client"; import { STATION_PICTURES_VERSION } from "$defs/constants"; import { page } from "$app/stores"; + import { GET, unwrap } from "$lib/client"; type Item = typeof data.stream_connections.items[number]; @@ -240,18 +241,22 @@ if(skip !== prev_skip) { logger.info(`(re)starting listeners update (document: ${document.visibilityState}, on_screen: ${on_screen})`); } + if(Date.now() - last_update < UPDATE_INTERVAL) continue; - try { - let _token = ++token; - const params: import("$api/stream-connections/GET/Query").Query = { - show: "open", - limit: 100_000, - sort: "creation-desc", - // stations: q_station_id ? [q_station_id] : undefined, - }; - - let stream_connections = await _get(`/api/stream-connections${qss(params)}`); + try { + const _token = ++token; + + const stream_connections = unwrap(await GET("/stream-connections", { + params: { + query: { + show: "open", + limit: 100_000, + sort: "creation-desc", + // stations: q_station_id ? [q_station_id] : undefined, + } + } + })); if(_token === token) { data.stream_connections = stream_connections; diff --git a/front/admin/src/routes/(online)/(app)/listeners/+page.ts b/front/admin/src/routes/(online)/(app)/listeners/+page.ts deleted file mode 100644 index a4c83c20..00000000 --- a/front/admin/src/routes/(online)/(app)/listeners/+page.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { load_get } from "$lib/load"; -import { qss } from "$share/qs"; - -export const load = (async ({ fetch, url }) => { - const params: import("$api/stream-connections/GET/Query").Query = { - show: "open", - limit: 100_000, - sort: "creation-desc", - } - - let stream_connections = await load_get( - `/api/stream-connections${qss(params)}`, - { fetch, url } - ); - - return { - stream_connections - } - -}) satisfies import("./$types").PageLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/me/api-keys/+page.server.ts b/front/admin/src/routes/(online)/(app)/me/api-keys/+page.server.ts index 9602bfa7..a813f8ab 100644 --- a/front/admin/src/routes/(online)/(app)/me/api-keys/+page.server.ts +++ b/front/admin/src/routes/(online)/(app)/me/api-keys/+page.server.ts @@ -1,12 +1,12 @@ -import { load_get } from "$lib/load"; +import { client, load_call } from "$lib/load"; -export const load = (async ({ fetch, url, depends }) => { +export const load = (async ({ fetch, depends }) => { depends("resource:api-keys"); depends("api:api-keys") // TODO: implement pagination - const api_keys = await load_get(`/api/me/api-keys?limit=10000`, { fetch, url }); + const api_keys = await load_call(() => client.GET("/me/api-keys", { params: { query: { limit: 100_000 } }, fetch })); return { api_keys } diff --git a/front/admin/src/routes/(online)/(app)/me/api-keys/+page.svelte b/front/admin/src/routes/(online)/(app)/me/api-keys/+page.svelte index a5cca81d..dbc2044b 100644 --- a/front/admin/src/routes/(online)/(app)/me/api-keys/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/me/api-keys/+page.svelte @@ -21,6 +21,7 @@ import Email from '$share/Form/Email.svelte'; import copy from 'copy-to-clipboard'; import { locale } from '$lib/locale'; + import { DELETE, unwrap } from '$lib/client'; $: current = data.api_keys.items.find(item => item.is_current); @@ -30,7 +31,7 @@ const delete_key = action(async () => { if (delete_item == null) return; - await _delete(`/api/me/api-keys/${delete_item._id}`); + unwrap(await DELETE("/me/api-keys/{id}", { params: { path: { "id": delete_item._id } } })); _message($locale.misc.api_keys.API_key_deleted); delete_item = null; invalidate('resource:api-keys'); diff --git a/front/admin/src/routes/(online)/(app)/me/devices/+page.server.ts b/front/admin/src/routes/(online)/(app)/me/devices/+page.server.ts new file mode 100644 index 00000000..73c99466 --- /dev/null +++ b/front/admin/src/routes/(online)/(app)/me/devices/+page.server.ts @@ -0,0 +1,15 @@ +import { client, load_call } from "$lib/load"; + +export const load = (async ({ fetch, depends }) => { + + depends("resource:devices"); + depends("api:devices") + + // TODO: implement pagination + const devices = await load_call( + () => client.GET("/me/devices", { params: { query: { limit: 100_000 } }, fetch }) + ); + + return { devices } + +}) satisfies import("./$types").PageServerLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/me/devices/+page.svelte b/front/admin/src/routes/(online)/(app)/me/devices/+page.svelte index a582f559..a13a592b 100644 --- a/front/admin/src/routes/(online)/(app)/me/devices/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/me/devices/+page.svelte @@ -14,6 +14,7 @@ import { invalidate } from '$app/navigation'; import Page from '$lib/components/Page.svelte'; import { locale } from '$lib/locale'; + import { DELETE, unwrap } from '$lib/client'; $: current = data.devices.items.find(item => item.is_current); @@ -23,7 +24,7 @@ const disconnect = action(async () => { if (disconnect_item == null) return; - await _delete(`/api/me/devices/${disconnect_item._id}`); + unwrap(await DELETE("/me/devices/{device}", { params: { path: { "device": disconnect_item._id } } })); _message($locale.pages['me/devices'].notifier.device_disconnected); disconnect_item = null; invalidate('resource:devices'); diff --git a/front/admin/src/routes/(online)/(app)/me/devices/+page.ts b/front/admin/src/routes/(online)/(app)/me/devices/+page.ts deleted file mode 100644 index 2fd28d24..00000000 --- a/front/admin/src/routes/(online)/(app)/me/devices/+page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { load_get } from "$lib/load"; -import { qss } from "$share/qs"; - -export const load = (async ({ fetch, url, depends }) => { - - depends("resource:devices"); - depends("api:devices") - - // TODO: implement pagination - const devices = await load_get( - `/api/me/devices${qss({ limit: 100_000 })}`, - { fetch, url } - ); - - return { devices } - -}) satisfies import("./$types").PageLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/me/devices/device.svelte b/front/admin/src/routes/(online)/(app)/me/devices/device.svelte index e5afd527..6ee0c7d2 100644 --- a/front/admin/src/routes/(online)/(app)/me/devices/device.svelte +++ b/front/admin/src/routes/(online)/(app)/me/devices/device.svelte @@ -8,18 +8,18 @@ import { tooltip } from "$share/tooltip"; import { mdiTrashCanOutline } from "@mdi/js"; - import icon_chrome from "$share/img/browser-icons/chrome.svg"; - import icon_firefox from "$share/img/browser-icons/firefox.svg"; - import icon_safari from "$share/img/browser-icons/safari.svg"; - import icon_edge from "$share/img/browser-icons/edge.svg"; - import icon_opera from "$share/img/browser-icons/opera.svg"; - import icon_other from "$share/img/browser-icons/other.svg"; - - import icon_linux from "$share/img/os-icons/linux.svg"; - import icon_android from "$share/img/os-icons/android.svg"; - import icon_windows from "$share/img/os-icons/windows.svg"; - import icon_osx from "$share/img/os-icons/osx.svg"; - import icon_ios from "$share/img/os-icons/ios.svg"; + import icon_chrome from "$share/img/browser-icons/chrome.png"; + import icon_firefox from "$share/img/browser-icons/firefox.png"; + import icon_safari from "$share/img/browser-icons/safari.png"; + import icon_edge from "$share/img/browser-icons/edge.png"; + import icon_opera from "$share/img/browser-icons/opera.png"; + import icon_other from "$share/img/browser-icons/other.png"; + + import icon_linux from "$share/img/os-icons/linux.png"; + import icon_android from "$share/img/os-icons/android.png"; + import icon_windows from "$share/img/os-icons/windows.png"; + import icon_osx from "$share/img/os-icons/osx.png"; + import icon_ios from "$share/img/os-icons/ios.png"; import Page from "$lib/components/Page.svelte"; import { locale, lang } from "$lib/locale"; diff --git a/front/admin/src/routes/(online)/(app)/me/me.svelte b/front/admin/src/routes/(online)/(app)/me/me.svelte index fbc87643..cb4ad565 100644 --- a/front/admin/src/routes/(online)/(app)/me/me.svelte +++ b/front/admin/src/routes/(online)/(app)/me/me.svelte @@ -24,6 +24,7 @@ import { tick } from "svelte"; import { locale } from "$lib/locale"; import { VALIDATE_ADMIN_FIRST_NAME_MAX_LEN, VALIDATE_ADMIN_LAST_NAME_MAX_LEN, VALIDATE_ADMIN_PASSWORD_MAX_LEN, VALIDATE_ADMIN_PASSWORD_MIN_LEN } from "$server/defs/constants"; + import { PATCH, POST, unwrap } from "$lib/client"; // import SelectField from "$share/Form/SelectField.svelte"; let show_change_password = true; @@ -62,15 +63,20 @@ else language = dif.language; } - // TODO: remove this partial - const payload: Partial = { - first_name: dif.first_name, - last_name: dif.last_name, - language, - // phone: dif.phone, - }; + unwrap(await PATCH(`/admins/{admin}`, { + params: { + path: { + admin: data.admin._id, + } + }, + body: { + first_name: dif.first_name, + last_name: dif.last_name, + language, + // phone: dif.phone, + } + })); - await _patch(`/api/admins/${data.admin._id}`, payload); profile_db = clone(profile_current); _message($locale.pages.me.notifier.profile_updated); invalidateAll(); @@ -84,12 +90,19 @@ if(current_password === "") throw new Error("Current password is required"); if(new_password === "") throw new Error("New password is required"); if(new_password !== confirm_new_password) throw new Error("Confirmation password doesn't match"); - const payload: import("$api/admins/[admin]/change-password/POST/Payload").Payload = { - current_password, - new_password, - }; + + unwrap(await POST(`/admins/{admin}/change-password`, { + params: { + path: { + admin: data.admin._id, + } + }, + body: { + current_password, + new_password, + } + })); - await _post(`/api/admins/${data.admin._id}/change-password`, payload); current_password = ""; new_password = ""; confirm_new_password = ""; diff --git a/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.ts b/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.server.ts similarity index 88% rename from front/admin/src/routes/(online)/(app)/plans/[plan]/+page.ts rename to front/admin/src/routes/(online)/(app)/plans/[plan]/+page.server.ts index 56902f57..007e463b 100644 --- a/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.ts +++ b/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.server.ts @@ -8,4 +8,4 @@ export const load = (async ({ parent, params }) => { } return { plan } -}) satisfies import("./$types").PageLoad; \ No newline at end of file +}) satisfies import("./$types").PageServerLoad; \ No newline at end of file diff --git a/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.svelte b/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.svelte index 278b5cd5..729a8c60 100644 --- a/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/plans/[plan]/+page.svelte @@ -12,6 +12,7 @@ import { mdiTrashCanOutline } from "@mdi/js"; import { goto } from "$app/navigation"; import Dialog from "$share/Dialog.svelte"; + import { DELETE, PATCH, unwrap } from "$lib/client"; let db = { identifier: data.plan.identifier, @@ -54,20 +55,30 @@ if(transfer === null) throw new Error("Transfer is required"); if(storage === null) throw new Error("Storage is required"); - const payload: import("$api/plans/[plan]/PATCH/Payload").Payload = { - identifier, - slug, - display_name, - color, - price, - stations, - listeners, - storage, - transfer, - is_user_selectable, - } + const plan = unwrap(await PATCH( + "/plans/{plan}", + { + params: { + path: { + plan: data.plan._id + } + }, + + body: { + identifier, + slug, + display_name, + color, + price, + stations, + listeners, + storage, + transfer, + is_user_selectable, + } + } + )); - const plan = await _patch(`/api/plans/${data.plan._id}`, payload); db = clone(current); _message("Plan updated"); invalidateAll(); @@ -76,7 +87,7 @@ let delete_open = false; const del = action(async () => { - await _delete(`/api/plans/${data.plan._id}`); + unwrap(await DELETE("/plans/{plan}", { params: { path: { plan: data.plan._id } } })); goto("/plans", { invalidateAll: true }) invalidate_siblings(); }) diff --git a/front/admin/src/routes/(online)/(app)/plans/create-plan/+page.svelte b/front/admin/src/routes/(online)/(app)/plans/create-plan/+page.svelte index af55d008..ff7cd718 100644 --- a/front/admin/src/routes/(online)/(app)/plans/create-plan/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/plans/create-plan/+page.svelte @@ -8,6 +8,7 @@ import { goto } from "$app/navigation"; import PlanForm from "../PlanForm.svelte"; import { invalidate_siblings } from "$lib/invalidate"; + import { POST, unwrap } from "$lib/client"; let db = { identifier: "", @@ -49,20 +50,22 @@ if(transfer === null) throw new Error("Transfer is required"); if(storage === null) throw new Error("Storage is required"); - const payload: import("$api/plans/POST/Payload").Payload = { - identifier, - display_name, - slug, - color, - price, - stations, - listeners, - storage, - transfer, - is_user_selectable, - } + unwrap(await POST( + "/plans", { + body: { + identifier, + display_name, + slug, + color, + price, + stations, + listeners, + storage, + transfer, + is_user_selectable, + } + })) - const plan = await _post(`/api/plans`, payload); db = clone(current); _message("New plan created"); saving = false; diff --git a/front/admin/src/routes/(online)/(app)/stations/[station]/+page.ts b/front/admin/src/routes/(online)/(app)/stations/[station]/+page.server.ts similarity index 54% rename from front/admin/src/routes/(online)/(app)/stations/[station]/+page.ts rename to front/admin/src/routes/(online)/(app)/stations/[station]/+page.server.ts index f330ae23..df4029c3 100644 --- a/front/admin/src/routes/(online)/(app)/stations/[station]/+page.ts +++ b/front/admin/src/routes/(online)/(app)/stations/[station]/+page.server.ts @@ -1,7 +1,7 @@ -import { load_get } from "$lib/load"; +import { client, load_call } from "$lib/load"; import { error } from "@sveltejs/kit"; -export const load = (async ({ parent, params, fetch, url }) => { +export const load = (async ({ parent, params, fetch }) => { const { all_stations, all_accounts } = await parent(); @@ -15,10 +15,10 @@ export const load = (async ({ parent, params, fetch, url }) => { now_playing, { stats }, ] = await Promise.all([ - load_get(`/api/stations/${params.station}/now-playing`, { fetch, url }), - load_get(`/api/stations/${params.station}/stream-stats`, { fetch, url }), + load_call(() => client.GET("/stations/{station}/now-playing", { params: { path: { station: params.station } }, fetch })), + load_call(() => client.GET("/stations/{station}/stream-stats", { params: { path: { station: params.station } }, fetch })), ]); return { station, account, now_playing, stats } -}) satisfies import("./$types").PageLoad; +}) satisfies import("./$types").PageServerLoad; diff --git a/front/admin/src/routes/(online)/(app)/stations/[station]/+page.svelte b/front/admin/src/routes/(online)/(app)/stations/[station]/+page.svelte index 50093430..42c2fd8b 100644 --- a/front/admin/src/routes/(online)/(app)/stations/[station]/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/stations/[station]/+page.svelte @@ -15,6 +15,7 @@ import { invalidate_siblings } from "$lib/invalidate"; import Dialog from "$share/Dialog.svelte"; import { STATION_PICTURES_VERSION } from "$defs/constants"; + import { DELETE, unwrap } from "$lib/client"; const date = (d: string | Date) => { @@ -36,7 +37,7 @@ if(deleting) return; deleting = true; try { - await _delete(`/api/stations/${data.station._id}`); + unwrap(await DELETE("/stations/{station}", { params: { path: { station: data.station._id } } })); delete_open = false; _message("Station deleted"); await goto("/stations", { invalidateAll: true }); diff --git a/front/admin/src/routes/(online)/(app)/users/[user]/+page.ts b/front/admin/src/routes/(online)/(app)/users/[user]/+page.server.ts similarity index 51% rename from front/admin/src/routes/(online)/(app)/users/[user]/+page.ts rename to front/admin/src/routes/(online)/(app)/users/[user]/+page.server.ts index b893037d..129cd9d8 100644 --- a/front/admin/src/routes/(online)/(app)/users/[user]/+page.ts +++ b/front/admin/src/routes/(online)/(app)/users/[user]/+page.server.ts @@ -1,8 +1,7 @@ -import { load_get } from "$lib/load"; -import { qss } from "$share/qs"; +import { client, load_call } from "$lib/load"; import { error } from "@sveltejs/kit"; -export const load = (async ({ parent, params, fetch, url }) => { +export const load = (async ({ parent, params, fetch }) => { const { all_users, all_stations } = await parent(); @@ -10,17 +9,14 @@ export const load = (async ({ parent, params, fetch, url }) => { if(user == null) error(404, { status: 404, message: "User not found", code: "FRONT_RESOURCE_NOT_FOUND" }); - const query = ({ - show: "all", - user_id: user._id - }) satisfies import("$server/defs/api/accounts/GET/Query").Query; + const user_accounts = await load_call( + () => client.GET("/accounts", { params: { query: { show: "all", user_id: user._id } }, fetch }) + ); - const user_accounts = await load_get(`/api/accounts?${qss(query)}`, { fetch, url }) - const user_stations = all_stations.filter(station => { return user_accounts.items.some(account => station.account_id === account._id); }) return { user, user_accounts, user_stations } -}) satisfies import("./$types").PageLoad; +}) satisfies import("./$types").PageServerLoad; diff --git a/front/admin/src/routes/(online)/(app)/users/[user]/+page.svelte b/front/admin/src/routes/(online)/(app)/users/[user]/+page.svelte index 9ae21c22..73ed3c78 100644 --- a/front/admin/src/routes/(online)/(app)/users/[user]/+page.svelte +++ b/front/admin/src/routes/(online)/(app)/users/[user]/+page.svelte @@ -12,6 +12,7 @@ import { ripple } from "$share/ripple"; import { mdiLogin, mdiTrashCanOutline } from "@mdi/js"; import { STATION_PICTURES_VERSION } from "$defs/constants"; + import { DELETE, unwrap } from "$lib/client"; const date = (d: string | Date) => { const date = new Date(d); @@ -43,7 +44,7 @@ if(deleting) return; deleting = true; try { - await _delete(`/api/users/${data.user._id}`); + unwrap(await DELETE("/users/{user}", { params: { path: { user: data.user._id } } })); delete_open = false; _message("User deleted"); await goto("/users", { invalidateAll: true }); diff --git a/front/admin/src/routes/(online)/(login)/recover/+page.svelte b/front/admin/src/routes/(online)/(login)/recover/+page.svelte index 1fd504dc..0c111d08 100644 --- a/front/admin/src/routes/(online)/(login)/recover/+page.svelte +++ b/front/admin/src/routes/(online)/(login)/recover/+page.svelte @@ -12,7 +12,7 @@ import { _progress } from "$share/notify"; import CircularProgress from "$share/CircularProgress.svelte"; import "$share/LoginDashboard/login-page.css"; - + let email = ""; let sent_to: string | null = null; let sending = false; @@ -21,11 +21,12 @@ if(sending) return; sending = true; try { - const payload: import("$api/auth/user/recover/POST/Payload").Payload = { email }; - await _post(`/api/auth/admin/recover`, payload); sent_to = email; email = ""; sending = false; + // TODO: implement this in backend + // unwrap(await POST("/auth/admin/recover", { body: { email } })); + throw new Error("This feature is not yet implemented"); } catch(e) { sending = false; throw e; diff --git a/front/admin/src/routes/(online)/+layout.ts b/front/admin/src/routes/(online)/+layout.server.ts similarity index 91% rename from front/admin/src/routes/(online)/+layout.ts rename to front/admin/src/routes/(online)/+layout.server.ts index 636c0de2..edf232ab 100644 --- a/front/admin/src/routes/(online)/+layout.ts +++ b/front/admin/src/routes/(online)/+layout.server.ts @@ -17,4 +17,4 @@ export const load = (async ({ depends, fetch, url }) => { return { config, locale, maybe_admin, } -}) satisfies import("./$types").LayoutLoad; \ No newline at end of file +}) satisfies import("./$types").LayoutServerLoad; \ No newline at end of file diff --git a/front/app/package-lock.json b/front/app/package-lock.json index df3bcacc..e973e1fc 100644 --- a/front/app/package-lock.json +++ b/front/app/package-lock.json @@ -24,6 +24,7 @@ "eslint-config-prettier": "^8.3.0", "http-status-codes": "^2.2.0", "kleur": "^4.1.5", + "openapi-fetch": "^0.8.2", "prettier": "^2.6.2", "prettier-plugin-svelte": "^2.10.1", "svelte": "^4.2.10", @@ -32,6 +33,7 @@ "ts-node": "^10.9.2", "ts-patch": "^3.1.2", "tslib": "^2.3.1", + "type-fest": "^4.10.2", "typescript": "^5.3.2", "typia": "^5.4.1", "vite": "^5.0.0", @@ -2577,6 +2579,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalyzer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", @@ -3294,6 +3308,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-fetch": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.8.2.tgz", + "integrity": "sha512-4g+NLK8FmQ51RW6zLcCBOVy/lwYmFJiiT+ckYZxJWxUxH4XFhsNcX2eeqVMfVOi+mDNFja6qDXIZAz2c5J/RVw==", + "dev": true, + "dependencies": { + "openapi-typescript-helpers": "^0.0.5" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.5.tgz", + "integrity": "sha512-MRffg93t0hgGZbYTxg60hkRIK2sRuEOHEtCUgMuLgbCC33TMQ68AmxskzUlauzZYD47+ENeGV/ElI7qnWqrAxA==", + "dev": true + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -4555,12 +4584,12 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz", + "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6541,6 +6570,14 @@ "dev": true, "requires": { "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, "globalyzer": { @@ -7079,6 +7116,21 @@ "mimic-fn": "^2.1.0" } }, + "openapi-fetch": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.8.2.tgz", + "integrity": "sha512-4g+NLK8FmQ51RW6zLcCBOVy/lwYmFJiiT+ckYZxJWxUxH4XFhsNcX2eeqVMfVOi+mDNFja6qDXIZAz2c5J/RVw==", + "dev": true, + "requires": { + "openapi-typescript-helpers": "^0.0.5" + } + }, + "openapi-typescript-helpers": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.5.tgz", + "integrity": "sha512-MRffg93t0hgGZbYTxg60hkRIK2sRuEOHEtCUgMuLgbCC33TMQ68AmxskzUlauzZYD47+ENeGV/ElI7qnWqrAxA==", + "dev": true + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -7942,9 +7994,9 @@ } }, "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz", + "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==", "dev": true }, "typescript": { diff --git a/front/app/package.json b/front/app/package.json index 0c7a177f..03a179f0 100644 --- a/front/app/package.json +++ b/front/app/package.json @@ -30,6 +30,7 @@ "eslint-config-prettier": "^8.3.0", "http-status-codes": "^2.2.0", "kleur": "^4.1.5", + "openapi-fetch": "^0.8.2", "prettier": "^2.6.2", "prettier-plugin-svelte": "^2.10.1", "svelte": "^4.2.10", @@ -38,6 +39,7 @@ "ts-node": "^10.9.2", "ts-patch": "^3.1.2", "tslib": "^2.3.1", + "type-fest": "^4.10.2", "typescript": "^5.3.2", "typia": "^5.4.1", "vite": "^5.0.0", diff --git a/front/app/src/hooks.server.ts b/front/app/src/hooks.server.ts index bb88acac..d7131136 100644 --- a/front/app/src/hooks.server.ts +++ b/front/app/src/hooks.server.ts @@ -29,14 +29,14 @@ export const handle: Handle = async ({ event, resolve }) => { event.locals.protocol = proto; - let buffer = ""; + let replaced = false; const res = await resolve(event, { transformPageChunk: ({ html, done }) => { - buffer += html; - if(done) { + if(!replaced && html.includes("%html_attrs%")) { + replaced = true; const lang = event.locals.lang || "en"; const dir = event.locals.dir || "ltr"; - return buffer.replace("%html_attrs%", `lang="${lang}" dir="${dir}"`); + return html.replace("%html_attrs%", `lang="${lang}" dir="${dir}"`); } else { return undefined; } @@ -67,12 +67,12 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { url.port = String(env.APP_API_PORT); url.protocol = `http:`; - server_logger.debug(`handle-fetch: ${event.request.url} => ${request.url} ip=${event.locals.ip} proto=${event.locals.protocol}`) + server_logger.info(`handle-fetch: ${event.request.url} => ${request.url} ip=${event.locals.ip} proto=${event.locals.protocol}`) - const target = new Request(request) + const headers = new Headers() const host = event.request.headers.get("host"); - if(host) target.headers.set("x-host", host); + if(host) headers.set("x-host", host); for(const key of [ "x-forwarded-proto", @@ -83,12 +83,12 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { "cookie" ]) { const v = event.request.headers.get(key); - if(v) target.headers.set(key, v); + if(v) headers.set(key, v); } - target.headers.set(FORWARD_IP_HEADER, event.locals.ip); - target.headers.set(PROTOCOL_HEADER, event.locals.protocol); - target.headers.set("x-kit-url", event.request.url); + headers.set(FORWARD_IP_HEADER, event.locals.ip); + headers.set(PROTOCOL_HEADER, event.locals.protocol); + headers.set("x-kit-url", event.request.url); // const src_cookies = (event.request.headers.get("cookie")?.split(";") || []).map(s => s.trim()); // const cookie = [...new Set([...src_cookies, ...event.locals.cookie])].join("; ").trim(); @@ -96,9 +96,9 @@ export const handleFetch: HandleFetch = async ({ event, request, fetch }) => { try { const res = await fetch(url, { - method: target.method, - headers: target.headers, - body: target.body, + method: request.method, + headers, + body: request.body, // mode: "same-origin" }); diff --git a/front/app/src/lib/client.ts b/front/app/src/lib/client.ts new file mode 100644 index 00000000..d2018523 --- /dev/null +++ b/front/app/src/lib/client.ts @@ -0,0 +1,17 @@ +import type { paths } from "../../../../openapi"; +import createClient from "openapi-fetch"; + +export const { GET, POST, PUT, PATCH, DELETE } = createClient({ + baseUrl: "/api/" +}) + +export type Unwrap = R extends { data: infer T } ? Exclude : never; + +export const unwrap = ( + result: + | { data: T, error?: undefined } + | { data?: undefined, error: { error: { status: number, code: string, message: string } } } +): NonNullable => { + if (result.error) throw new Error(result.error.error.message) + return result.data!; +} \ No newline at end of file diff --git a/front/app/src/lib/components/Dashboard/TopUser.svelte b/front/app/src/lib/components/Dashboard/TopUser.svelte index 0d72b418..035d67a6 100644 --- a/front/app/src/lib/components/Dashboard/TopUser.svelte +++ b/front/app/src/lib/components/Dashboard/TopUser.svelte @@ -22,9 +22,10 @@ import { locale } from "$lib/locale"; import { invalidate_siblings } from "$lib/invalidate"; import { logical_fly } from "$share/transition"; + import { POST, unwrap } from "$lib/client"; const sign_out = action(async () => { - await _post("/api/auth/user/logout", {}); + unwrap(await POST("/auth/user/logout")) goto("/", { invalidateAll: true }) invalidate_siblings(); }) diff --git a/front/app/src/lib/components/PlanSelector/PlanSelector.svelte b/front/app/src/lib/components/PlanSelector/PlanSelector.svelte index 5782c18f..a4ce3a07 100644 --- a/front/app/src/lib/components/PlanSelector/PlanSelector.svelte +++ b/front/app/src/lib/components/PlanSelector/PlanSelector.svelte @@ -4,7 +4,9 @@ export let select_btn_label: string; export let show_trial: boolean = false; - import type { Plan } from "$server/defs/db/Plan"; + import type { Unwrap, GET } from "$lib/client"; + // import type { Plan } from "$server/defs/db/Plan"; + type Plan = Unwrap>>>["plan"] import Icon from "$share/Icon.svelte"; import { ripple } from "$share/ripple"; diff --git a/front/app/src/lib/components/Player/player.ts b/front/app/src/lib/components/Player/player.ts index 7016f7c5..9c3927d0 100644 --- a/front/app/src/lib/components/Player/player.ts +++ b/front/app/src/lib/components/Player/player.ts @@ -1,12 +1,12 @@ import { browser } from "$app/environment"; import { default_logger } from "$share/logger"; import { get_now_playing_store, type NowPlaying } from "$lib/now-playing"; -import { _get } from "$share/net.client"; import { derived, get, writable } from "svelte/store"; import { page } from "$app/stores"; import { equals } from "$server/util/collections"; import { locale } from "$share/locale"; import { STATION_PICTURES_VERSION } from "$server/defs/constants"; +import type { Unwrap, GET } from "$lib/client"; export type PlayerState = PlayerState.Closed | PlayerState.Station | PlayerState.AudioFile; @@ -35,10 +35,12 @@ export namespace PlayerState { } } + export type AudioFileItem = Unwrap>>>["item"]; + export interface AudioFile extends Base { type: "track" audio_state: AudioState, - file: import("$server/defs/db/AudioFile").AudioFile + file: AudioFileItem, picture_id: string, } } @@ -119,7 +121,7 @@ export const player_title = derived(player_state, (state): string => { else return assert_never(state); }) -export const player_subtitle = derived([player_state, now_playing], ([state, now_playing]): string | null => { +export const player_subtitle = derived([player_state, now_playing], ([state, now_playing]): string | null | undefined => { if (state.type === "closed") return null; else if (state.type === "track") return state.file.metadata.artist; else if (state.type === "station") { @@ -334,7 +336,7 @@ if (hasMediaSession) { }) } -export const play_track = (file: import("$server/defs/db/AudioFile").AudioFile, picture_id: string) => { +export const play_track = (file: PlayerState.AudioFileItem, picture_id: string) => { if (!browser) throw new Error("player.play_track called in ssr context"); destroy_audio_tag(); now_playing_stop(); diff --git a/front/app/src/lib/components/StationProfile.svelte b/front/app/src/lib/components/StationProfile.svelte index 740f2367..d4e4383f 100644 --- a/front/app/src/lib/components/StationProfile.svelte +++ b/front/app/src/lib/components/StationProfile.svelte @@ -60,35 +60,35 @@ export let account_id: string; export let current: { - picture_id: string | null; + picture_id: string | null | undefined; - name: string | null; - slogan: string | null; - description: string | null; + name: string | null | undefined; + slogan: string | null | undefined; + description: string | null | undefined; country_code: CountryCode | ""; lang_code: LangCode | "", type_of_content: StationTypeOfContent | "", - frequency: StationFrequency | null, - - email: string | null; - phone: string | null; - whatsapp: string | null; - - website_url: string | null; - twitter_url: string | null; - facebook_url: string | null; - instagram_url: string | null; - threads_url: string | null; - youtube_url: string | null; - twitch_url: string | null; - tiktok_url: string | null; - spotify_url: string | null; - radiocut_url: string | null, - - google_play_url: string | null; - app_store_url: string | null; + frequency: StationFrequency | null | undefined, + + email: string | null | undefined; + phone: string | null | undefined; + whatsapp: string | null | undefined; + + website_url: string | null | undefined; + twitter_url: string | null | undefined; + facebook_url: string | null | undefined; + instagram_url: string | null | undefined; + threads_url: string | null | undefined; + youtube_url: string | null | undefined; + twitch_url: string | null | undefined; + tiktok_url: string | null | undefined; + spotify_url: string | null | undefined; + radiocut_url: string | null | undefined, + + google_play_url: string | null | undefined; + app_store_url: string | null | undefined; user_metadata: { mob_app: { @@ -96,12 +96,12 @@ icon_bg_color: string icon_rounded: boolean ads: boolean - admob_app_id: string | null - admob_banner_id: string | null + admob_app_id: string | null | undefined + admob_banner_id: string | null | undefined - google_play_title: string | null - google_play_subtitle: string | null - google_play_description: string | null + google_play_title: string | null | undefined + google_play_subtitle: string | null | undefined + google_play_description: string | null | undefined google_play_lang: GooglePlayLang | "" } } @@ -109,7 +109,7 @@ export let advanced_open: boolean = false; - const _validate_admob_app_id = (value: string | null): string | null => { + const _validate_admob_app_id = (value: string | null | undefined): string | null => { if(value == null) return null; if(!/^ca\-app\-pub\-[0-9]{16}\~[0-9]{10}$/.test(value)) { return $locale.station_profile.validation.admob_app_id_pattern; @@ -117,7 +117,7 @@ return null; } - const _validate_admob_banner_id = (value: string | null): string | null => { + const _validate_admob_banner_id = (value: string | null | undefined): string | null => { if(value == null) return null; if(!/^ca\-app\-pub\-[0-9]{16}\/[0-9]{10}$/.test(value)) { return $locale.station_profile.validation.admob_banner_id_pattern; diff --git a/front/app/src/lib/css/app.css b/front/app/src/lib/css/app.css index d6411c77..8873df02 100644 --- a/front/app/src/lib/css/app.css +++ b/front/app/src/lib/css/app.css @@ -110,7 +110,7 @@ button, input[type="submit"] { padding: 0; border: 0; background: transparent; - appareance: none; + appearance: none; cursor: pointer; } diff --git a/front/app/src/lib/load.ts b/front/app/src/lib/load.ts index 2fff4fa0..9ef2dfb5 100644 --- a/front/app/src/lib/load.ts +++ b/front/app/src/lib/load.ts @@ -5,11 +5,44 @@ import StatusCode from "http-status-codes"; export type User = import("$server/defs/db/PublicUser").PublicUser & { media_key: string }; +import type { paths } from "../../../../openapi"; +import createClient from "openapi-fetch"; +export const client = createClient({ + baseUrl: browser ? "/api/" : "https://internal.test/api/", +}); + +export const load_call = async ( + fn: () => Promise< + | { data: T, error?: undefined } + | { data?: undefined, error: { error: { status: number, code: import("$defs/error/PublicErrorCode").PublicErrorCode, message: string } } } + >, + { redirectToLoginOnAuthErrors = true } = {} +): Promise> => { + try { + const result = await fn(); + if(result.error === undefined) { + return result.data!; + } else { + if(result.error?.error?.status === StatusCode.UNAUTHORIZED && redirectToLoginOnAuthErrors) { + const to = `${location.pathname}${location.search}`; + const login_url = to === "/" ? "/login" : `/login#${to}` + redirect(302, login_url); + } else { + const api_error = ApiError.from_error_payload(result.error); + error(api_error.status as NumericRange<400, 599>, api_error.toJSON().error); + } + } + } catch(e: any) { + const api_error = new ApiError(502, "FRONT_GATEWAY_FETCH", `Bad gateway (fetch)`); + error(api_error.status as NumericRange<400, 599>, api_error.toJSON().error); + } +} + export const load_get_me = async ( { fetch, url }: Pick ): Promise => { try { - const { user, media_key }: { user: import("$server/defs/db/PublicUser").PublicUser, media_key: string } = await load_get("/api/users/me", { fetch, url }, { redirectToLoginOnAuthErrors: false }); + const { user, media_key }: { user: import("$server/defs/db/PublicUser").PublicUser, media_key: string } = await load_get_internal("/api/users/me", { fetch, url }, { redirectToLoginOnAuthErrors: false }); return { ...user, media_key } } catch (e: any) { if(e?.status === StatusCode.UNAUTHORIZED) { @@ -20,7 +53,7 @@ export const load_get_me = async ( } } -export const load_get = async ( +export const load_get_internal = async ( _target: string, { fetch, url }: Pick, { redirectToLoginOnAuthErrors = true } = {} @@ -54,45 +87,45 @@ export const load_get = async ( return body as T } -export const load_with_payload = async ( - method: "POST" | "PUT" | "PATCH", - _target: string, - payload: any, - { fetch, url }: Pick, - { redirectToLoginOnAuthErrors = true } = {} -): Promise => { +// export const load_with_payload = async ( +// method: "POST" | "PUT" | "PATCH", +// _target: string, +// payload: any, +// { fetch, url }: Pick, +// { redirectToLoginOnAuthErrors = true } = {} +// ): Promise => { - const target = browser ? _target : `${url.origin}${_target}` +// const target = browser ? _target : `${url.origin}${_target}` - const res = await fetch(target, { - method, - headers: { "content-type": "application/json" }, - body: JSON.stringify(payload), - }).catch((_e) => { - const e = new ApiError(StatusCode.BAD_GATEWAY, "FRONT_GATEWAY_FETCH", "Bad gateway (fetch)"); - error(e.status as NumericRange<400, 599>, e.toJSON().error); - }) +// const res = await fetch(target, { +// method, +// headers: { "content-type": "application/json" }, +// body: JSON.stringify(payload), +// }).catch((_e) => { +// const e = new ApiError(StatusCode.BAD_GATEWAY, "FRONT_GATEWAY_FETCH", "Bad gateway (fetch)"); +// error(e.status as NumericRange<400, 599>, e.toJSON().error); +// }) - const body: any = await res.json().catch((_e) => { - const e = new ApiError(StatusCode.BAD_GATEWAY, "FRONT_GATEWAY_JSON", "Bad gateway (json)"); - error(e.status as NumericRange<400, 599>, e.toJSON().error); - }) +// const body: any = await res.json().catch((_e) => { +// const e = new ApiError(StatusCode.BAD_GATEWAY, "FRONT_GATEWAY_JSON", "Bad gateway (json)"); +// error(e.status as NumericRange<400, 599>, e.toJSON().error); +// }) - if(redirectToLoginOnAuthErrors) { - if(res.status === StatusCode.UNAUTHORIZED) { - const to = `${url.pathname}${url.search}`; - const login_url = to === "/" ? "/login" : `/login#${target}` - redirect(302, login_url); - } - } +// if(redirectToLoginOnAuthErrors) { +// if(res.status === StatusCode.UNAUTHORIZED) { +// const to = `${url.pathname}${url.search}`; +// const login_url = to === "/" ? "/login" : `/login#${target}` +// redirect(302, login_url); +// } +// } - if(body.error) { - const e = ApiError.from_error_payload(body.error); - error(e.status as NumericRange<400, 599>, e.toJSON().error); - } +// if(body.error) { +// const e = ApiError.from_error_payload(body.error); +// error(e.status as NumericRange<400, 599>, e.toJSON().error); +// } - return body as T -} +// return body as T +// } export const not_found_load = () => { error(404, { status: 404, message: "Page not found", code: "CLIENT_PAGE_NOT_FOUND" }); diff --git a/front/app/src/lib/now-playing.ts b/front/app/src/lib/now-playing.ts index d12d894e..fa74f77f 100644 --- a/front/app/src/lib/now-playing.ts +++ b/front/app/src/lib/now-playing.ts @@ -1,10 +1,12 @@ import { browser } from "$app/environment"; -import { _get } from "$share/net.client"; import { type Readable, type Writable, readable, writable } from "svelte/store"; import { default_logger } from "$share/logger"; import { sleep } from "$share/util"; +import { GET, unwrap } from "./client"; +import type { Unwrap } from "./client"; -export type NowPlaying = import("$api/stations/[station]/now-playing/GET/Output").Output; +// export type NowPlaying = import("$api/stations/[station]/now-playing/GET/Output").Output; +export type NowPlaying = Unwrap>>>; export type StoreValue = { station_id: string, info: NowPlaying }; export const NOW_PLAYING_INTERNVAL = 3000; @@ -50,7 +52,11 @@ export const get_now_playing_store = (station_id: string, default_info: NowPlayi if(Date.now() - last < NOW_PLAYING_INTERNVAL) continue; try { - const info = await _get(`/api/stations/${station_id}/now-playing`); + const info = unwrap(await GET("/stations/{station}/now-playing", { + params: { + path: { station: station_id } + } + })); logger.info(`info updated for ${station_id}`) store.set({ station_id, info }); } catch(e) { diff --git a/front/app/src/routes/(root)/(online)/(app)/+layout.server.ts b/front/app/src/routes/(root)/(online)/(app)/+layout.server.ts index 2cf5a0be..e4863f65 100644 --- a/front/app/src/routes/(root)/(online)/(app)/+layout.server.ts +++ b/front/app/src/routes/(root)/(online)/(app)/+layout.server.ts @@ -1,4 +1,4 @@ -import { load_get } from "$lib/load"; +import { load_call, client } from "$lib/load"; import { redirect } from "@sveltejs/kit"; export const load = (async ({ fetch, url, parent, depends }) => { @@ -18,8 +18,8 @@ export const load = (async ({ fetch, url, parent, depends }) => { accounts, stations, ] = await Promise.all([ - load_get(`/api/accounts?limit=10000`, { fetch, url }), - load_get(`/api/stations?limit=10000`, { fetch, url }), + load_call(() => client.GET("/accounts", { params: { query: { limit: 10_000 } }, fetch })), + load_call(() => client.GET("/stations", { params: { query: { limit: 10_000 } }, fetch })), ]) diff --git a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+layout.server.ts b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+layout.server.ts index 65f6daf0..3e751bb9 100644 --- a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+layout.server.ts +++ b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+layout.server.ts @@ -1,10 +1,13 @@ -import { load_get } from "$lib/load"; +import { client, load_call } from "$lib/load"; -export const load = (async ({ fetch, url, depends, params }) => { +export const load = (async ({ fetch, depends, params }) => { depends("api:accounts/:id"); - const { account, is_owner }: import("$api/accounts/[account]/GET/Output").Output = await load_get(`/api/accounts/${params.account}`, { fetch, url }); + const { account, is_owner } = await load_call(async () => await client.GET("/accounts/{account}", { + params: { path: { account: params.account } }, + fetch, + })); return { account, diff --git a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.server.ts b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.server.ts index d7126d5d..a7ed6516 100644 --- a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.server.ts +++ b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.server.ts @@ -1,6 +1,6 @@ -import { load_get } from "$lib/load"; +import { client, load_call } from "$lib/load"; -export const load = (async ({ fetch, url, parent, depends, params }) => { +export const load = (async ({ fetch, parent, depends, params }) => { depends("api:accounts/:id/stream-stats"); depends("api:accounts/:id/stream-stats/now/count-by-station"); @@ -10,8 +10,8 @@ export const load = (async ({ fetch, url, parent, depends, params }) => { { stats }, { by_station: sessions_by_station }, ] = await Promise.all([ - load_get(`/api/accounts/${params.account}/stream-stats`, { fetch, url }), - load_get(`/api/accounts/${params.account}/stream-stats/now/count-by-station`, { fetch, url }), + load_call(() => client.GET("/accounts/{account}/stream-stats", { params: { path: { account: params.account } }, fetch })), + load_call(() => client.GET("/accounts/{account}/stream-stats/now/count-by-station", { params: { path: { account: params.account } }, fetch })), ]) const now_playing_record = await (async () => { @@ -26,7 +26,8 @@ export const load = (async ({ fetch, url, parent, depends, params }) => { // fetch the now playing of first 30 satations (approx the maximum visible ones on non-scrolled screen) await Promise.all(to_fetch_stations.map(async station => { - const now_playing = await load_get(`/api/stations/${station._id}/now-playing`, { url, fetch }); + const now_playing = await load_call(() => client.GET("/stations/{station}/now-playing", { params: { path: { station: station._id } }, fetch })); + // @ts-ignore now_playing_record[station._id] = now_playing; })) diff --git a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.svelte b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.svelte index 1aa6e897..d337b059 100644 --- a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.svelte +++ b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/+page.svelte @@ -42,6 +42,7 @@ import { onMount } from "svelte"; import { VALIDATE_ACCOUNT_NAME_MAX_LEN } from "$server/defs/constants"; import { flip } from "svelte/animate"; + import { GET, PATCH, unwrap } from "$lib/client"; let view: View = "now"; @@ -130,7 +131,7 @@ } if(Date.now() - last < LIMITS_UPDATE_INTERVAL) continue; try { - const limits: AccountLimits = await _get(`/api/accounts/${data.account._id}/limits`); + const { account: { limits } } = unwrap(await GET(`/accounts/{account}`, { params: { path: { account: data.account._id } } })); logger.info(`account limits updated`); data.account.limits = limits; } catch(e) { @@ -150,10 +151,7 @@ let edit_open = false; let current_account_name = data.account.name; const edit = action(async () => { - let payload: import("$api/accounts/[account]/PATCH/Payload").Payload = { - name: current_account_name, - }; - await _patch(`/api/accounts/${data.account._id}`, payload); + unwrap(await PATCH("/accounts/{account}", { params: { path: { account: data.account._id } }, body: { name: current_account_name } })); data.account.name = current_account_name; edit_open = false; }) diff --git a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/account-station-item.svelte b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/account-station-item.svelte index e7311b31..2d31acec 100644 --- a/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/account-station-item.svelte +++ b/front/app/src/routes/(root)/(online)/(app)/accounts/[account]/account-station-item.svelte @@ -1,19 +1,17 @@