diff --git a/app/client/api-client/client/client.gen.ts b/app/client/api-client/client/client.gen.ts index 7bddcbe1..e21ca7c4 100644 --- a/app/client/api-client/client/client.gen.ts +++ b/app/client/api-client/client/client.gen.ts @@ -32,7 +32,14 @@ export const createClient = (config: Config = {}): Client => { const interceptors = createInterceptors(); - const beforeRequest = async (options: RequestOptions) => { + const beforeRequest = async < + TData = unknown, + TResponseStyle extends 'data' | 'fields' = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, + >( + options: RequestOptions, + ) => { const opts = { ..._config, ...options, @@ -61,13 +68,14 @@ export const createClient = (config: Config = {}): Client => { opts.headers.delete('Content-Type'); } - const url = buildUrl(opts); + const resolvedOpts = opts as typeof opts & + ResolvedRequestOptions; + const url = buildUrl(resolvedOpts); - return { opts, url }; + return { opts: resolvedOpts, url }; }; const request: Client['request'] = async (options) => { - // @ts-expect-error const { opts, url } = await beforeRequest(options); const requestInit: ReqInit = { redirect: 'follow', diff --git a/app/client/api-client/client/types.gen.ts b/app/client/api-client/client/types.gen.ts index 92f86372..7d2edad9 100644 --- a/app/client/api-client/client/types.gen.ts +++ b/app/client/api-client/client/types.gen.ts @@ -153,7 +153,7 @@ type SseFn = < ThrowOnError extends boolean = false, TResponseStyle extends ResponseStyle = 'fields', >( - options: Omit, 'method'>, + options: Omit, 'method'>, ) => Promise>; type RequestFn = < diff --git a/app/client/api-client/client/utils.gen.ts b/app/client/api-client/client/utils.gen.ts index e1cb1851..0cbe3272 100644 --- a/app/client/api-client/client/utils.gen.ts +++ b/app/client/api-client/client/utils.gen.ts @@ -205,7 +205,7 @@ export const mergeHeaders = ( mergedHeaders.append(key, v as string); } } else if (value !== undefined) { - // assume object headers are meant to be JSON stringified, i.e. their + // assume object headers are meant to be JSON stringified, i.e., their // content value in OpenAPI specification is 'application/json' mergedHeaders.set( key, diff --git a/app/client/api-client/core/types.gen.ts b/app/client/api-client/core/types.gen.ts index c6d572e5..8dd55e14 100644 --- a/app/client/api-client/core/types.gen.ts +++ b/app/client/api-client/core/types.gen.ts @@ -81,7 +81,7 @@ export interface Config { requestValidator?: (data: unknown) => Promise; /** * A function transforming response data before it's returned. This is useful - * for post-processing data, e.g. converting ISO strings into Date objects. + * for post-processing data, e.g., converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; /** diff --git a/app/client/api-client/core/utils.gen.ts b/app/client/api-client/core/utils.gen.ts index 10452388..99f21554 100644 --- a/app/client/api-client/core/utils.gen.ts +++ b/app/client/api-client/core/utils.gen.ts @@ -127,7 +127,7 @@ export function getValidRequestBody(options: { return hasSerializedBody ? options.serializedBody : null; } - // not all clients implement a serializedBody property (i.e. client-axios) + // not all clients implement a serializedBody property (i.e., client-axios) return options.body !== '' ? options.body : null; } diff --git a/app/client/api-client/sdk.gen.ts b/app/client/api-client/sdk.gen.ts index b1c29713..de0e764f 100644 --- a/app/client/api-client/sdk.gen.ts +++ b/app/client/api-client/sdk.gen.ts @@ -3,9 +3,9 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { BrowseFilesystemData, BrowseFilesystemResponses, CancelDoctorData, CancelDoctorErrors, CancelDoctorResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteSnapshotsData, DeleteSnapshotsResponses, DeleteSsoInvitationData, DeleteSsoInvitationErrors, DeleteSsoInvitationResponses, DeleteSsoProviderData, DeleteSsoProviderErrors, DeleteSsoProviderResponses, DeleteUserAccountData, DeleteUserAccountErrors, DeleteUserAccountResponses, DeleteVolumeData, DeleteVolumeResponses, DevPanelExecData, DevPanelExecErrors, DevPanelExecResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, DumpSnapshotData, DumpSnapshotResponses, GetAdminUsersData, GetAdminUsersResponses, GetBackupProgressData, GetBackupProgressResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetDevPanelData, GetDevPanelResponses, GetMirrorCompatibilityData, GetMirrorCompatibilityResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetOrgMembersData, GetOrgMembersResponses, GetPublicSsoProvidersData, GetPublicSsoProvidersResponses, GetRegistrationStatusData, GetRegistrationStatusResponses, GetRepositoryData, GetRepositoryResponses, GetRepositoryStatsData, GetRepositoryStatsResponses, GetScheduleMirrorsData, GetScheduleMirrorsResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetSsoSettingsData, GetSsoSettingsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetUpdatesData, GetUpdatesResponses, GetUserDeletionImpactData, GetUserDeletionImpactResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, MountVolumeData, MountVolumeResponses, RefreshRepositoryStatsData, RefreshRepositoryStatsResponses, RefreshSnapshotsData, RefreshSnapshotsResponses, RemoveOrgMemberData, RemoveOrgMemberErrors, RemoveOrgMemberResponses, ReorderBackupSchedulesData, ReorderBackupSchedulesResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, SetRegistrationStatusData, SetRegistrationStatusResponses, StartDoctorData, StartDoctorErrors, StartDoctorResponses, StopBackupData, StopBackupErrors, StopBackupResponses, TagSnapshotsData, TagSnapshotsResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnlockRepositoryData, UnlockRepositoryResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateMemberRoleData, UpdateMemberRoleErrors, UpdateMemberRoleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateRepositoryData, UpdateRepositoryErrors, UpdateRepositoryResponses, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateSsoProviderAutoLinkingData, UpdateSsoProviderAutoLinkingErrors, UpdateSsoProviderAutoLinkingResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen'; +import type { BrowseFilesystemData, BrowseFilesystemResponses, CancelDoctorData, CancelDoctorErrors, CancelDoctorResponses, CreateBackupScheduleData, CreateBackupScheduleResponses, CreateNotificationDestinationData, CreateNotificationDestinationResponses, CreateRepositoryData, CreateRepositoryResponses, CreateVolumeData, CreateVolumeResponses, DeleteBackupScheduleData, DeleteBackupScheduleResponses, DeleteNotificationDestinationData, DeleteNotificationDestinationErrors, DeleteNotificationDestinationResponses, DeleteRepositoryData, DeleteRepositoryResponses, DeleteSnapshotData, DeleteSnapshotResponses, DeleteSnapshotsData, DeleteSnapshotsResponses, DeleteSsoInvitationData, DeleteSsoInvitationErrors, DeleteSsoInvitationResponses, DeleteSsoProviderData, DeleteSsoProviderErrors, DeleteSsoProviderResponses, DeleteUserAccountData, DeleteUserAccountErrors, DeleteUserAccountResponses, DeleteVolumeData, DeleteVolumeResponses, DevPanelExecData, DevPanelExecErrors, DevPanelExecResponse, DevPanelExecResponses, DownloadResticPasswordData, DownloadResticPasswordResponses, DumpSnapshotData, DumpSnapshotResponses, GetAdminUsersData, GetAdminUsersResponses, GetBackupProgressData, GetBackupProgressResponses, GetBackupScheduleData, GetBackupScheduleForVolumeData, GetBackupScheduleForVolumeResponses, GetBackupScheduleResponses, GetDevPanelData, GetDevPanelResponses, GetMirrorCompatibilityData, GetMirrorCompatibilityResponses, GetNotificationDestinationData, GetNotificationDestinationErrors, GetNotificationDestinationResponses, GetOrgMembersData, GetOrgMembersResponses, GetPublicSsoProvidersData, GetPublicSsoProvidersResponses, GetRegistrationStatusData, GetRegistrationStatusResponses, GetRepositoryData, GetRepositoryResponses, GetRepositoryStatsData, GetRepositoryStatsResponses, GetScheduleMirrorsData, GetScheduleMirrorsResponses, GetScheduleNotificationsData, GetScheduleNotificationsResponses, GetSnapshotDetailsData, GetSnapshotDetailsResponses, GetSsoSettingsData, GetSsoSettingsResponses, GetStatusData, GetStatusResponses, GetSystemInfoData, GetSystemInfoResponses, GetUpdatesData, GetUpdatesResponses, GetUserDeletionImpactData, GetUserDeletionImpactResponses, GetVolumeData, GetVolumeErrors, GetVolumeResponses, HealthCheckVolumeData, HealthCheckVolumeErrors, HealthCheckVolumeResponses, ListBackupSchedulesData, ListBackupSchedulesResponses, ListFilesData, ListFilesResponses, ListNotificationDestinationsData, ListNotificationDestinationsResponses, ListRcloneRemotesData, ListRcloneRemotesResponses, ListRepositoriesData, ListRepositoriesResponses, ListSnapshotFilesData, ListSnapshotFilesResponses, ListSnapshotsData, ListSnapshotsResponses, ListVolumesData, ListVolumesResponses, MountVolumeData, MountVolumeResponses, RefreshRepositoryStatsData, RefreshRepositoryStatsResponses, RefreshSnapshotsData, RefreshSnapshotsResponses, RemoveOrgMemberData, RemoveOrgMemberErrors, RemoveOrgMemberResponses, ReorderBackupSchedulesData, ReorderBackupSchedulesResponses, RestoreSnapshotData, RestoreSnapshotResponses, RunBackupNowData, RunBackupNowResponses, RunForgetData, RunForgetResponses, SetRegistrationStatusData, SetRegistrationStatusResponses, StartDoctorData, StartDoctorErrors, StartDoctorResponses, StopBackupData, StopBackupErrors, StopBackupResponses, TagSnapshotsData, TagSnapshotsResponses, TestConnectionData, TestConnectionResponses, TestNotificationDestinationData, TestNotificationDestinationErrors, TestNotificationDestinationResponses, UnlockRepositoryData, UnlockRepositoryResponses, UnmountVolumeData, UnmountVolumeResponses, UpdateBackupScheduleData, UpdateBackupScheduleResponses, UpdateMemberRoleData, UpdateMemberRoleErrors, UpdateMemberRoleResponses, UpdateNotificationDestinationData, UpdateNotificationDestinationErrors, UpdateNotificationDestinationResponses, UpdateRepositoryData, UpdateRepositoryErrors, UpdateRepositoryResponses, UpdateScheduleMirrorsData, UpdateScheduleMirrorsResponses, UpdateScheduleNotificationsData, UpdateScheduleNotificationsResponses, UpdateSsoProviderAutoLinkingData, UpdateSsoProviderAutoLinkingErrors, UpdateSsoProviderAutoLinkingResponses, UpdateVolumeData, UpdateVolumeErrors, UpdateVolumeResponses } from './types.gen'; -export type Options = Options2 & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a @@ -307,7 +307,7 @@ export const tagSnapshots = (options: Opti /** * Execute a restic command against a repository (dev panel only) */ -export const devPanelExec = (options: Options) => (options.client ?? client).sse.post({ +export const devPanelExec = (options: Options) => (options.client ?? client).sse.post({ url: '/api/v1/repositories/{shortId}/exec', ...options, headers: { diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 48cbca56..b9aa2748 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -3526,7 +3526,6 @@ export type GetScheduleNotificationsResponses = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -3628,7 +3627,6 @@ export type UpdateScheduleNotificationsResponses = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -4203,7 +4201,6 @@ export type ListNotificationDestinationsResponses = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -4273,7 +4270,6 @@ export type CreateNotificationDestinationData = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -4348,7 +4344,6 @@ export type CreateNotificationDestinationResponses = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -4467,7 +4462,6 @@ export type GetNotificationDestinationResponses = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -4538,7 +4532,6 @@ export type UpdateNotificationDestinationData = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { @@ -4622,7 +4615,6 @@ export type UpdateNotificationDestinationResponses = { } | { type: 'slack'; webhookUrl: string; - channel?: string; username?: string; iconEmoji?: string; } | { diff --git a/app/client/modules/notifications/components/create-notification-form.tsx b/app/client/modules/notifications/components/create-notification-form.tsx index 66451dc1..6b8c1cdf 100644 --- a/app/client/modules/notifications/components/create-notification-form.tsx +++ b/app/client/modules/notifications/components/create-notification-form.tsx @@ -75,7 +75,6 @@ const defaultValuesForType = { slack: { type: "slack" as const, webhookUrl: "", - channel: "", username: "", iconEmoji: "", }, diff --git a/app/client/modules/notifications/components/notification-forms/slack-form.tsx b/app/client/modules/notifications/components/notification-forms/slack-form.tsx index 4fc91b1a..31129244 100644 --- a/app/client/modules/notifications/components/notification-forms/slack-form.tsx +++ b/app/client/modules/notifications/components/notification-forms/slack-form.tsx @@ -27,20 +27,6 @@ export const SlackForm = ({ form }: Props) => { )} /> - ( - - Channel (Optional) - - - - Override the default channel (use # for channels, @ for users). - - - )} - /> } label="Webhook URL" value={config.webhookUrl} mono /> - } label="Channel" value={config.channel || "—"} /> } label="Bot Username" value={config.username || "—"} /> } label="Icon Emoji" value={config.iconEmoji || "—"} /> diff --git a/app/schemas/notifications.ts b/app/schemas/notifications.ts index d93db511..62d90287 100644 --- a/app/schemas/notifications.ts +++ b/app/schemas/notifications.ts @@ -29,7 +29,6 @@ export const emailNotificationConfigSchema = z.object({ export const slackNotificationConfigSchema = z.object({ type: z.literal("slack"), webhookUrl: z.string().min(1), - channel: z.string().optional(), username: z.string().optional(), iconEmoji: z.string().optional(), }); diff --git a/app/server/modules/notifications/__tests__/notification-builders.test.ts b/app/server/modules/notifications/__tests__/notification-builders.test.ts index 64062919..58bc74ea 100644 --- a/app/server/modules/notifications/__tests__/notification-builders.test.ts +++ b/app/server/modules/notifications/__tests__/notification-builders.test.ts @@ -152,7 +152,7 @@ describe("notification shoutrrr URL builders", () => { ).toBe("pushover://shoutrrr:api-token@user-key/?devices=iphone%2Cipad&priority=1"); }); - test("builds slack URLs and rejects invalid webhook formats", () => { + test("builds upstream-compatible slack webhook URLs and rejects invalid webhook formats", () => { expect( buildSlackShoutrrrUrl({ type: "slack", @@ -164,11 +164,10 @@ describe("notification shoutrrr URL builders", () => { buildSlackShoutrrrUrl({ type: "slack", webhookUrl: "https://hooks.slack.com/services/T000/B000/XXX", - channel: "#alerts", username: "Alert Bot", iconEmoji: ":robot_face:", }), - ).toBe("slack://hook:T000-B000-XXX@webhook?channel=%23alerts&username=Alert+Bot&icon_emoji=%3Arobot_face%3A"); + ).toBe("slack://hook:T000-B000-XXX@webhook?username=Alert+Bot&icon_emoji=%3Arobot_face%3A"); expect(() => buildSlackShoutrrrUrl({ diff --git a/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts b/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts index b7fa81dd..1657661b 100644 --- a/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts +++ b/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts @@ -7,7 +7,6 @@ describe("mapNotificationConfigSecrets", () => { { type: "slack", webhookUrl: "https://hooks.slack.test/services/a/b/c", - channel: "#alerts", username: "zerobyte", iconEmoji: ":wave:", }, @@ -17,7 +16,6 @@ describe("mapNotificationConfigSecrets", () => { expect(transformed).toEqual({ type: "slack", webhookUrl: "sealed:https://hooks.slack.test/services/a/b/c", - channel: "#alerts", username: "zerobyte", iconEmoji: ":wave:", }); diff --git a/app/server/modules/notifications/builders/slack.ts b/app/server/modules/notifications/builders/slack.ts index f7a5d337..6b38eb44 100644 --- a/app/server/modules/notifications/builders/slack.ts +++ b/app/server/modules/notifications/builders/slack.ts @@ -14,9 +14,6 @@ export const buildSlackShoutrrrUrl = (config: Extract