@muhammedaksam/easiarr 1.3.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/package.json +8 -6
- package/src/api/arr-api.ts +22 -9
- package/src/api/auto-setup-types.ts +1 -1
- package/src/api/base-api.ts +216 -0
- package/src/api/bazarr-api.ts +53 -112
- package/src/api/cloudflare-api.ts +92 -176
- package/src/api/custom-format-api.ts +15 -3
- package/src/api/grafana-api.ts +32 -116
- package/src/api/heimdall-api.ts +15 -83
- package/src/api/homarr-api.ts +33 -120
- package/src/api/huntarr-api.ts +47 -212
- package/src/api/index.ts +8 -0
- package/src/api/jellyfin-api.ts +49 -133
- package/src/api/jellyseerr-api.ts +45 -146
- package/src/api/maintainerr-api.ts +80 -350
- package/src/api/naming-config.ts +10 -2
- package/src/api/overseerr-api.ts +28 -149
- package/src/api/plex-api.ts +23 -73
- package/src/api/portainer-api.ts +41 -134
- package/src/api/profilarr-api.ts +16 -5
- package/src/api/prowlarr-api.ts +55 -426
- package/src/api/qbittorrent-api.ts +34 -73
- package/src/api/quality-profile-api.ts +7 -4
- package/src/api/tautulli-api.ts +13 -46
- package/src/api/uptime-kuma-api.ts +5 -4
- package/src/apps/categories.ts +1 -1
- package/src/apps/index.ts +2 -2
- package/src/apps/registry.ts +33 -13
- package/src/compose/caddy-config.ts +5 -4
- package/src/compose/generator.ts +38 -13
- package/src/compose/index.ts +7 -3
- package/src/compose/traefik-config.ts +5 -3
- package/src/config/bookmarks-generator.ts +21 -10
- package/src/config/defaults.ts +1 -1
- package/src/config/homepage-config.ts +13 -8
- package/src/config/index.ts +4 -1
- package/src/config/manager.ts +6 -5
- package/src/config/recyclarr-config.ts +5 -3
- package/src/config/schema.ts +1 -1
- package/src/config/slskd-config.ts +3 -2
- package/src/config/soularr-config.ts +1 -1
- package/src/data/lidarr-custom-formats.ts +1 -1
- package/src/data/trash-profiles.ts +8 -1
- package/src/docker/client.ts +34 -13
- package/src/docker/index.ts +18 -1
- package/src/index.ts +1 -1
- package/src/setup/actions/arr-common.ts +307 -0
- package/src/setup/actions/dashboards.ts +79 -0
- package/src/setup/actions/download-clients.ts +54 -0
- package/src/setup/actions/jellyseerr.ts +331 -0
- package/src/setup/actions/media-servers.ts +68 -0
- package/src/setup/actions/monitoring.ts +120 -0
- package/src/setup/actions/prowlarr.ts +137 -0
- package/src/setup/actions/utilities.ts +490 -0
- package/src/setup/index.ts +78 -0
- package/src/setup/types.ts +144 -0
- package/src/structure/manager.ts +3 -2
- package/src/ui/App.ts +12 -10
- package/src/ui/components/ApplicationSelector.ts +11 -8
- package/src/ui/components/CredentialsForm.ts +180 -0
- package/src/ui/components/DownloadClientForm.ts +114 -0
- package/src/ui/components/FileEditor.ts +4 -4
- package/src/ui/components/PageLayout.ts +13 -5
- package/src/ui/components/UpdateNotification.ts +3 -2
- package/src/ui/index.ts +7 -1
- package/src/ui/screens/AdvancedSettings.ts +19 -12
- package/src/ui/screens/ApiKeyViewer.ts +21 -10
- package/src/ui/screens/AppConfigurator.ts +156 -344
- package/src/ui/screens/AppManager.ts +18 -10
- package/src/ui/screens/ContainerControl.ts +14 -12
- package/src/ui/screens/LogsViewer.ts +17 -7
- package/src/ui/screens/MainMenu.ts +34 -23
- package/src/ui/screens/MonitorDashboard.ts +29 -14
- package/src/ui/screens/SecretsEditor.ts +11 -9
- package/src/ui/screens/SettingsScreen.ts +42 -14
- package/src/ui/screens/index.ts +19 -1
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +375 -0
- package/src/ui/screens/setup/CloudflaredSetup.ts +460 -0
- package/src/ui/screens/setup/FullAutoSetup.ts +766 -0
- package/src/ui/screens/setup/HomepageSetup.ts +165 -0
- package/src/ui/screens/setup/JellyfinSetup.ts +328 -0
- package/src/ui/screens/setup/JellyseerrSetup.ts +242 -0
- package/src/ui/screens/{ProwlarrSetup.ts → setup/ProwlarrSetup.ts} +278 -401
- package/src/ui/screens/setup/QBittorrentSetup.ts +221 -0
- package/src/ui/screens/{QuickSetup.ts → setup/QuickSetup.ts} +22 -14
- package/src/ui/screens/setup/RecyclarrSetup.ts +270 -0
- package/src/ui/screens/{TRaSHProfileSetup.ts → setup/TRaSHProfileSetup.ts} +204 -218
- package/src/{util → utils}/arch.ts +1 -1
- package/src/utils/browser.ts +2 -1
- package/src/utils/categories.ts +4 -2
- package/src/utils/debug.ts +3 -10
- package/src/utils/env.ts +3 -2
- package/src/utils/index.ts +8 -0
- package/src/utils/logs.ts +5 -4
- package/src/utils/migrations/1765626338_rename_env_variables.ts +4 -3
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +3 -2
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +3 -2
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +10 -3
- package/src/utils/migrations.ts +2 -1
- package/src/utils/paths.ts +33 -0
- package/src/utils/unraid.ts +6 -4
- package/src/utils/update-checker.ts +4 -4
- package/src/utils/url-utils.ts +1 -1
- package/src/ui/screens/CloudflaredSetup.ts +0 -974
- package/src/ui/screens/FullAutoSetup.ts +0 -1719
- package/src/ui/screens/HomepageSetup.ts +0 -306
- package/src/ui/screens/JellyfinSetup.ts +0 -471
- package/src/ui/screens/JellyseerrSetup.ts +0 -612
- package/src/ui/screens/QBittorrentSetup.ts +0 -268
- package/src/ui/screens/RecyclarrSetup.ts +0 -418
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
> ⚠️ **Work In Progress** - This project is in early experimental development. Features may be incomplete, unstable, or change without notice.
|
|
12
12
|
|
|
13
|
-
TUI tool for generating docker-compose files for the \*arr media ecosystem with
|
|
13
|
+
TUI tool for generating docker-compose files for the \*arr media ecosystem with 52 apps, TRaSH Guides best practices, VPN routing, and Traefik/Caddy reverse proxy support.
|
|
14
14
|
|
|
15
15
|
A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and other \*arr applications with Docker Compose, following best practices from [TRaSH Guides](https://trash-guides.info/).
|
|
16
16
|
|
|
@@ -23,7 +23,7 @@ A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and othe
|
|
|
23
23
|
- 🎮 **Container Control** - Start, stop, and restart containers directly from the TUI
|
|
24
24
|
- 📋 **Container Logs Viewer** - View and save Docker container logs from the TUI
|
|
25
25
|
- ⚙️ **App Management** - Add or remove apps from your stack with ease
|
|
26
|
-
- 💾 **Persistent Configuration** - Settings saved to
|
|
26
|
+
- 💾 **Persistent Configuration** - Settings saved to `$XDG_CONFIG_HOME/easiarr/config.json`
|
|
27
27
|
- 🔀 **Reverse Proxy** - Traefik or Caddy support with automatic SSL
|
|
28
28
|
- 🖥️ **Unraid Support** - Automatic OS detection and compatibility
|
|
29
29
|
|
|
@@ -56,7 +56,7 @@ bun run start
|
|
|
56
56
|
- [Bun](https://bun.sh/) >= 1.0
|
|
57
57
|
- [Docker](https://www.docker.com/) with Docker Compose v2
|
|
58
58
|
|
|
59
|
-
## Supported Applications (
|
|
59
|
+
## Supported Applications (52 apps across 10 categories)
|
|
60
60
|
|
|
61
61
|
### Media Management (Servarr)
|
|
62
62
|
|
|
@@ -79,6 +79,8 @@ bun run start
|
|
|
79
79
|
|
|
80
80
|
- **qBittorrent** - BitTorrent client
|
|
81
81
|
- **SABnzbd** - Usenet downloader
|
|
82
|
+
- **Slskd** - Soulseek client for music sharing
|
|
83
|
+
- **Soularr** - Lidarr integration for Soulseek
|
|
82
84
|
|
|
83
85
|
### Media Servers
|
|
84
86
|
|
|
@@ -103,6 +105,7 @@ bun run start
|
|
|
103
105
|
- **Portainer** - Docker container management UI
|
|
104
106
|
- **Huntarr** - Missing content manager for \*arr apps
|
|
105
107
|
- **Unpackerr** - Archive extraction for \*arr apps
|
|
108
|
+
- **Maintainerr** - Automated media cleanup manager
|
|
106
109
|
- **Recyclarr** - TRaSH Guides profile sync (CLI-based)
|
|
107
110
|
- **Profilarr** - TRaSH Guides profile sync (Web UI)
|
|
108
111
|
- **FileBot** - Media file renaming and automator
|
|
@@ -160,10 +163,10 @@ The wizard will automatically:
|
|
|
160
163
|
|
|
161
164
|
## Configuration
|
|
162
165
|
|
|
163
|
-
easiarr stores its configuration in
|
|
166
|
+
easiarr stores its configuration in `$XDG_CONFIG_HOME/easiarr/`:
|
|
164
167
|
|
|
165
168
|
```bash
|
|
166
|
-
|
|
169
|
+
$XDG_CONFIG_HOME/easiarr/
|
|
167
170
|
├── config.json # Your easiarr configuration
|
|
168
171
|
├── docker-compose.yml # Generated Docker Compose file
|
|
169
172
|
└── backups/ # Configuration backups
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhammedaksam/easiarr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -51,23 +51,25 @@
|
|
|
51
51
|
"start": "bun run src/index.ts"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@eslint/js": "^
|
|
54
|
+
"@eslint/js": "^10.0.1",
|
|
55
|
+
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
|
55
56
|
"@types/bcrypt": "^6.0.0",
|
|
56
57
|
"@types/bun": "latest",
|
|
57
58
|
"@types/jest": "^30.0.0",
|
|
58
|
-
"@types/node": "^25.
|
|
59
|
-
"eslint": "^
|
|
59
|
+
"@types/node": "^25.2.3",
|
|
60
|
+
"eslint": "^10.0.0",
|
|
61
|
+
"eslint-plugin-import": "^2.32.0",
|
|
60
62
|
"jest": "^30.2.0",
|
|
61
63
|
"jiti": "^2.6.1",
|
|
62
64
|
"prettier": "^3.8.1",
|
|
63
65
|
"ts-jest": "^29.4.6",
|
|
64
|
-
"typescript-eslint": "^8.
|
|
66
|
+
"typescript-eslint": "^8.55.0"
|
|
65
67
|
},
|
|
66
68
|
"peerDependencies": {
|
|
67
69
|
"typescript": "^5.9.3"
|
|
68
70
|
},
|
|
69
71
|
"dependencies": {
|
|
70
|
-
"@opentui/core": "^0.1.
|
|
72
|
+
"@opentui/core": "^0.1.79",
|
|
71
73
|
"bcrypt": "^6.0.0",
|
|
72
74
|
"socket.io-client": "^4.8.3",
|
|
73
75
|
"yaml": "^2.8.2"
|
package/src/api/arr-api.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* Interacts with Radarr, Sonarr, Lidarr, Readarr, Whisparr APIs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import type { NamingConfig } from "./naming-config"
|
|
7
|
+
import type { AppId } from "~/config/schema"
|
|
8
|
+
import { getCategoryFieldName, getCategoryForApp } from "~/utils/categories"
|
|
9
|
+
import { debugLog } from "~/utils/debug"
|
|
10
|
+
import { TRASH_NAMING_CONFIG } from "./naming-config"
|
|
7
11
|
|
|
8
12
|
// Types for Root Folder API
|
|
9
13
|
export interface RootFolder {
|
|
@@ -44,10 +48,6 @@ export interface RemotePathMapping {
|
|
|
44
48
|
localPath: string
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
import type { AppId } from "../config/schema"
|
|
48
|
-
import { getCategoryForApp, getCategoryFieldName } from "../utils/categories"
|
|
49
|
-
import { TRASH_NAMING_CONFIG, type NamingConfig } from "./naming-config"
|
|
50
|
-
|
|
51
51
|
// qBittorrent download client config
|
|
52
52
|
export function createQBittorrentConfig(
|
|
53
53
|
host: string,
|
|
@@ -82,7 +82,12 @@ export function createQBittorrentConfig(
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// SABnzbd download client config
|
|
85
|
-
export function createSABnzbdConfig(
|
|
85
|
+
export function createSABnzbdConfig(
|
|
86
|
+
host: string,
|
|
87
|
+
port: number,
|
|
88
|
+
apiKey: string,
|
|
89
|
+
appId?: AppId
|
|
90
|
+
): DownloadClientConfig {
|
|
86
91
|
const category = appId ? getCategoryForApp(appId) : "default"
|
|
87
92
|
const categoryField = appId ? getCategoryFieldName(appId) : "category"
|
|
88
93
|
|
|
@@ -214,7 +219,11 @@ export class ArrApiClient {
|
|
|
214
219
|
return this.request<HostConfig>("/config/host")
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
async updateHostConfig(
|
|
222
|
+
async updateHostConfig(
|
|
223
|
+
username: string,
|
|
224
|
+
password: string,
|
|
225
|
+
override = false
|
|
226
|
+
): Promise<HostConfig | null> {
|
|
218
227
|
// First get current config to preserve all other settings
|
|
219
228
|
const currentConfig = await this.getHostConfig()
|
|
220
229
|
|
|
@@ -293,11 +302,15 @@ export class ArrApiClient {
|
|
|
293
302
|
// 3. Update configuration
|
|
294
303
|
await this.updateNamingConfig(newConfig)
|
|
295
304
|
} catch (e) {
|
|
296
|
-
throw new Error(`Failed to configure naming: ${e}
|
|
305
|
+
throw new Error(`Failed to configure naming: ${e}`, { cause: e })
|
|
297
306
|
}
|
|
298
307
|
}
|
|
299
308
|
|
|
300
|
-
async addRemotePathMapping(
|
|
309
|
+
async addRemotePathMapping(
|
|
310
|
+
host: string,
|
|
311
|
+
remotePath: string,
|
|
312
|
+
localPath: string
|
|
313
|
+
): Promise<RemotePathMapping> {
|
|
301
314
|
return this.request<RemotePathMapping>("/remotepathmapping", {
|
|
302
315
|
method: "POST",
|
|
303
316
|
body: JSON.stringify({ host, remotePath, localPath }),
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base API Client
|
|
3
|
+
* Abstract class providing shared fetch, logging, and error handling for all API clients.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { debugLog } from "~/utils/debug"
|
|
7
|
+
|
|
8
|
+
export interface ApiResponse<T> {
|
|
9
|
+
success: boolean
|
|
10
|
+
data?: T
|
|
11
|
+
error?: string
|
|
12
|
+
status?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export abstract class BaseApiClient {
|
|
16
|
+
protected host: string
|
|
17
|
+
protected port: number
|
|
18
|
+
protected abstract readonly logPrefix: string
|
|
19
|
+
|
|
20
|
+
constructor(host: string, port: number) {
|
|
21
|
+
this.host = host
|
|
22
|
+
this.port = port
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base URL for the API (override in subclass if protocol differs)
|
|
27
|
+
*/
|
|
28
|
+
protected get baseUrl(): string {
|
|
29
|
+
return `http://${this.host}:${this.port}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build API URL for a given endpoint
|
|
34
|
+
*/
|
|
35
|
+
protected buildUrl(endpoint: string, basePath: string = ""): string {
|
|
36
|
+
return `${this.baseUrl}${basePath}${endpoint}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generic GET request with logging and error handling
|
|
41
|
+
*/
|
|
42
|
+
protected async get<T>(
|
|
43
|
+
endpoint: string,
|
|
44
|
+
options: { basePath?: string; headers?: Record<string, string> } = {}
|
|
45
|
+
): Promise<ApiResponse<T>> {
|
|
46
|
+
const url = this.buildUrl(endpoint, options.basePath)
|
|
47
|
+
try {
|
|
48
|
+
debugLog(this.logPrefix, `GET ${endpoint}`)
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
method: "GET",
|
|
51
|
+
headers: options.headers,
|
|
52
|
+
})
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
debugLog(this.logPrefix, `GET ${endpoint} failed: ${response.status}`)
|
|
55
|
+
return { success: false, status: response.status }
|
|
56
|
+
}
|
|
57
|
+
const data = (await response.json()) as T
|
|
58
|
+
return { success: true, data }
|
|
59
|
+
} catch (error) {
|
|
60
|
+
debugLog(this.logPrefix, `GET ${endpoint} error: ${error}`)
|
|
61
|
+
return { success: false, error: String(error) }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generic GET request returning plain text
|
|
67
|
+
*/
|
|
68
|
+
protected async getText(
|
|
69
|
+
endpoint: string,
|
|
70
|
+
options: { basePath?: string; headers?: Record<string, string> } = {}
|
|
71
|
+
): Promise<ApiResponse<string>> {
|
|
72
|
+
const url = this.buildUrl(endpoint, options.basePath)
|
|
73
|
+
try {
|
|
74
|
+
debugLog(this.logPrefix, `GET (text) ${endpoint}`)
|
|
75
|
+
const response = await fetch(url, {
|
|
76
|
+
method: "GET",
|
|
77
|
+
headers: options.headers,
|
|
78
|
+
})
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
debugLog(this.logPrefix, `GET ${endpoint} failed: ${response.status}`)
|
|
81
|
+
return { success: false, status: response.status }
|
|
82
|
+
}
|
|
83
|
+
const data = await response.text()
|
|
84
|
+
return { success: true, data }
|
|
85
|
+
} catch (error) {
|
|
86
|
+
debugLog(this.logPrefix, `GET ${endpoint} error: ${error}`)
|
|
87
|
+
return { success: false, error: String(error) }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generic POST request with logging and error handling
|
|
93
|
+
*/
|
|
94
|
+
protected async post<T, B = unknown>(
|
|
95
|
+
endpoint: string,
|
|
96
|
+
body?: B,
|
|
97
|
+
options: { basePath?: string; headers?: Record<string, string> } = {}
|
|
98
|
+
): Promise<ApiResponse<T>> {
|
|
99
|
+
const url = this.buildUrl(endpoint, options.basePath)
|
|
100
|
+
try {
|
|
101
|
+
debugLog(this.logPrefix, `POST ${endpoint}`)
|
|
102
|
+
const response = await fetch(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
...options.headers,
|
|
107
|
+
},
|
|
108
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
109
|
+
})
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
debugLog(this.logPrefix, `POST ${endpoint} failed: ${response.status}`)
|
|
112
|
+
return { success: false, status: response.status }
|
|
113
|
+
}
|
|
114
|
+
// Handle empty responses
|
|
115
|
+
const text = await response.text()
|
|
116
|
+
if (!text) {
|
|
117
|
+
return { success: true }
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const data = JSON.parse(text) as T
|
|
121
|
+
return { success: true, data }
|
|
122
|
+
} catch {
|
|
123
|
+
// Response was not JSON
|
|
124
|
+
return { success: true }
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
debugLog(this.logPrefix, `POST ${endpoint} error: ${error}`)
|
|
128
|
+
return { success: false, error: String(error) }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generic PUT request with logging and error handling
|
|
134
|
+
*/
|
|
135
|
+
protected async put<T, B = unknown>(
|
|
136
|
+
endpoint: string,
|
|
137
|
+
body?: B,
|
|
138
|
+
options: { basePath?: string; headers?: Record<string, string> } = {}
|
|
139
|
+
): Promise<ApiResponse<T>> {
|
|
140
|
+
const url = this.buildUrl(endpoint, options.basePath)
|
|
141
|
+
try {
|
|
142
|
+
debugLog(this.logPrefix, `PUT ${endpoint}`)
|
|
143
|
+
const response = await fetch(url, {
|
|
144
|
+
method: "PUT",
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
...options.headers,
|
|
148
|
+
},
|
|
149
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
150
|
+
})
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
debugLog(this.logPrefix, `PUT ${endpoint} failed: ${response.status}`)
|
|
153
|
+
return { success: false, status: response.status }
|
|
154
|
+
}
|
|
155
|
+
const text = await response.text()
|
|
156
|
+
if (!text) {
|
|
157
|
+
return { success: true }
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const data = JSON.parse(text) as T
|
|
161
|
+
return { success: true, data }
|
|
162
|
+
} catch {
|
|
163
|
+
return { success: true }
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
debugLog(this.logPrefix, `PUT ${endpoint} error: ${error}`)
|
|
167
|
+
return { success: false, error: String(error) }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generic DELETE request with logging and error handling
|
|
173
|
+
*/
|
|
174
|
+
protected async delete<T>(
|
|
175
|
+
endpoint: string,
|
|
176
|
+
options: { basePath?: string; headers?: Record<string, string> } = {}
|
|
177
|
+
): Promise<ApiResponse<T>> {
|
|
178
|
+
const url = this.buildUrl(endpoint, options.basePath)
|
|
179
|
+
try {
|
|
180
|
+
debugLog(this.logPrefix, `DELETE ${endpoint}`)
|
|
181
|
+
const response = await fetch(url, {
|
|
182
|
+
method: "DELETE",
|
|
183
|
+
headers: options.headers,
|
|
184
|
+
})
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
debugLog(this.logPrefix, `DELETE ${endpoint} failed: ${response.status}`)
|
|
187
|
+
return { success: false, status: response.status }
|
|
188
|
+
}
|
|
189
|
+
const text = await response.text()
|
|
190
|
+
if (!text) {
|
|
191
|
+
return { success: true }
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const data = JSON.parse(text) as T
|
|
195
|
+
return { success: true, data }
|
|
196
|
+
} catch {
|
|
197
|
+
return { success: true }
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
debugLog(this.logPrefix, `DELETE ${endpoint} error: ${error}`)
|
|
201
|
+
return { success: false, error: String(error) }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Simple health check - override in subclass for custom logic
|
|
207
|
+
*/
|
|
208
|
+
async isHealthy(): Promise<boolean> {
|
|
209
|
+
try {
|
|
210
|
+
const response = await fetch(this.baseUrl, { method: "HEAD" })
|
|
211
|
+
return response.ok
|
|
212
|
+
} catch {
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|