cross-seed 6.13.6 → 7.0.0-10

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.
Files changed (255) hide show
  1. package/dist/Result.d.ts +27 -0
  2. package/dist/action.d.ts +34 -0
  3. package/dist/action.js +2 -1
  4. package/dist/action.js.map +1 -1
  5. package/dist/arr.d.ts +31 -0
  6. package/dist/arr.js +107 -39
  7. package/dist/arr.js.map +1 -1
  8. package/dist/auth.d.ts +3 -0
  9. package/dist/auth.js +9 -6
  10. package/dist/auth.js.map +1 -1
  11. package/dist/clients/Deluge.d.ts +153 -0
  12. package/dist/clients/Deluge.js +8 -7
  13. package/dist/clients/Deluge.js.map +1 -1
  14. package/dist/clients/QBittorrent.d.ts +218 -0
  15. package/dist/clients/QBittorrent.js +3 -2
  16. package/dist/clients/QBittorrent.js.map +1 -1
  17. package/dist/clients/RTorrent.d.ts +43 -0
  18. package/dist/clients/RTorrent.js +8 -4
  19. package/dist/clients/RTorrent.js.map +1 -1
  20. package/dist/clients/TorrentClient.d.ts +108 -0
  21. package/dist/clients/TorrentClient.js +137 -67
  22. package/dist/clients/TorrentClient.js.map +1 -1
  23. package/dist/clients/Transmission.d.ts +43 -0
  24. package/dist/clients/Transmission.js +4 -3
  25. package/dist/clients/Transmission.js.map +1 -1
  26. package/dist/cmd.d.ts +2 -0
  27. package/dist/cmd.js +42 -110
  28. package/dist/cmd.js.map +1 -1
  29. package/dist/configSchema.d.ts +1 -0
  30. package/dist/configSchema.js +1 -666
  31. package/dist/configSchema.js.map +1 -1
  32. package/dist/configuration.d.ts +63 -0
  33. package/dist/configuration.js +263 -24
  34. package/dist/configuration.js.map +1 -1
  35. package/dist/constants.d.ts +108 -0
  36. package/dist/constants.js +2 -32
  37. package/dist/constants.js.map +1 -1
  38. package/dist/dataFiles.d.ts +8 -0
  39. package/dist/dataFiles.js +21 -6
  40. package/dist/dataFiles.js.map +1 -1
  41. package/dist/db.d.ts +14 -0
  42. package/dist/db.js +76 -5
  43. package/dist/db.js.map +1 -1
  44. package/dist/dbConfig.d.ts +4 -0
  45. package/dist/dbConfig.js +67 -0
  46. package/dist/dbConfig.js.map +1 -0
  47. package/dist/decide.d.ts +25 -0
  48. package/dist/decide.js +4 -4
  49. package/dist/decide.js.map +1 -1
  50. package/dist/diagnostics/db.d.ts +21 -0
  51. package/dist/diagnostics/db.js +107 -0
  52. package/dist/diagnostics/db.js.map +1 -0
  53. package/dist/diff.d.ts +1 -0
  54. package/dist/errors.d.ts +3 -0
  55. package/dist/errors.js +0 -9
  56. package/dist/errors.js.map +1 -1
  57. package/dist/indexers.d.ts +105 -0
  58. package/dist/indexers.js +82 -14
  59. package/dist/indexers.js.map +1 -1
  60. package/dist/inject.d.ts +2 -0
  61. package/dist/jobs.d.ts +29 -0
  62. package/dist/jobs.js +14 -9
  63. package/dist/jobs.js.map +1 -1
  64. package/dist/logger.d.ts +29 -0
  65. package/dist/logger.js +18 -4
  66. package/dist/logger.js.map +1 -1
  67. package/dist/migrations/00-initialSchema.d.ts +9 -0
  68. package/dist/migrations/01-jobs.d.ts +9 -0
  69. package/dist/migrations/02-timestamps.d.ts +9 -0
  70. package/dist/migrations/03-rateLimits.d.ts +9 -0
  71. package/dist/migrations/04-auth.d.ts +9 -0
  72. package/dist/migrations/04-auth.js +1 -1
  73. package/dist/migrations/04-auth.js.map +1 -1
  74. package/dist/migrations/05-caps.d.ts +9 -0
  75. package/dist/migrations/06-uniqueDecisions.d.ts +9 -0
  76. package/dist/migrations/07-limits.d.ts +9 -0
  77. package/dist/migrations/08-rss.d.ts +9 -0
  78. package/dist/migrations/09-clientAndDataSearchees.d.ts +9 -0
  79. package/dist/migrations/10-indexerNameAudioBookCaps.d.ts +9 -0
  80. package/dist/migrations/11-trackers.d.ts +9 -0
  81. package/dist/migrations/12-user-auth.d.ts +9 -0
  82. package/dist/migrations/12-user-auth.js +22 -0
  83. package/dist/migrations/12-user-auth.js.map +1 -0
  84. package/dist/migrations/13-settings.d.ts +9 -0
  85. package/dist/migrations/13-settings.js +23 -0
  86. package/dist/migrations/13-settings.js.map +1 -0
  87. package/dist/migrations/14-indexer-enabled-flag.d.ts +9 -0
  88. package/dist/migrations/14-indexer-enabled-flag.js +12 -0
  89. package/dist/migrations/14-indexer-enabled-flag.js.map +1 -0
  90. package/dist/migrations/15-remove-url-unique-constraint.d.ts +9 -0
  91. package/dist/migrations/15-remove-url-unique-constraint.js +14 -0
  92. package/dist/migrations/15-remove-url-unique-constraint.js.map +1 -0
  93. package/dist/migrations/16-prune-inactive-indexers.d.ts +9 -0
  94. package/dist/migrations/16-prune-inactive-indexers.js +17 -0
  95. package/dist/migrations/16-prune-inactive-indexers.js.map +1 -0
  96. package/dist/migrations/migrations.d.ts +13 -0
  97. package/dist/migrations/migrations.js +10 -0
  98. package/dist/migrations/migrations.js.map +1 -1
  99. package/dist/parseTorrent.d.ts +53 -0
  100. package/dist/pipeline.d.ts +41 -0
  101. package/dist/pipeline.js +64 -13
  102. package/dist/pipeline.js.map +1 -1
  103. package/dist/preFilter.d.ts +25 -0
  104. package/dist/preFilter.js +15 -15
  105. package/dist/preFilter.js.map +1 -1
  106. package/dist/problems/linking.d.ts +2 -0
  107. package/dist/problems/linking.js +80 -0
  108. package/dist/problems/linking.js.map +1 -0
  109. package/dist/problems/path.d.ts +22 -0
  110. package/dist/problems/path.js +96 -0
  111. package/dist/problems/path.js.map +1 -0
  112. package/dist/problems.d.ts +13 -0
  113. package/dist/problems.js +48 -0
  114. package/dist/problems.js.map +1 -0
  115. package/dist/pushNotifier.d.ts +19 -0
  116. package/dist/routes/baseApi.d.ts +2 -0
  117. package/dist/routes/baseApi.js +354 -0
  118. package/dist/routes/baseApi.js.map +1 -0
  119. package/dist/routes/indexerApi.d.ts +6 -0
  120. package/dist/routes/indexerApi.js +165 -0
  121. package/dist/routes/indexerApi.js.map +1 -0
  122. package/dist/routes/staticFrontendPlugin.d.ts +4 -0
  123. package/dist/routes/staticFrontendPlugin.js +61 -0
  124. package/dist/routes/staticFrontendPlugin.js.map +1 -0
  125. package/dist/runtimeConfig.d.ts +6 -0
  126. package/dist/runtimeConfig.js +17 -1
  127. package/dist/runtimeConfig.js.map +1 -1
  128. package/dist/searchee.d.ts +108 -0
  129. package/dist/searchee.js +38 -6
  130. package/dist/searchee.js.map +1 -1
  131. package/dist/server.d.ts +4 -0
  132. package/dist/server.js +38 -429
  133. package/dist/server.js.map +1 -1
  134. package/dist/services/indexerService.d.ts +96 -0
  135. package/dist/services/indexerService.js +287 -0
  136. package/dist/services/indexerService.js.map +1 -0
  137. package/dist/sessionCookies.d.ts +5 -0
  138. package/dist/sessionCookies.js +27 -0
  139. package/dist/sessionCookies.js.map +1 -0
  140. package/dist/startup.d.ts +25 -0
  141. package/dist/startup.js +105 -151
  142. package/dist/startup.js.map +1 -1
  143. package/dist/torrent.d.ts +69 -0
  144. package/dist/torrent.js +17 -13
  145. package/dist/torrent.js.map +1 -1
  146. package/dist/torznab.d.ts +60 -0
  147. package/dist/torznab.js +14 -89
  148. package/dist/torznab.js.map +1 -1
  149. package/dist/trpc/fastifyAdapter.d.ts +2 -0
  150. package/dist/trpc/fastifyAdapter.js +9 -0
  151. package/dist/trpc/fastifyAdapter.js.map +1 -0
  152. package/dist/trpc/index.d.ts +49 -0
  153. package/dist/trpc/index.js +53 -0
  154. package/dist/trpc/index.js.map +1 -0
  155. package/dist/trpc/routers/auth.d.ts +43 -0
  156. package/dist/trpc/routers/auth.js +116 -0
  157. package/dist/trpc/routers/auth.js.map +1 -0
  158. package/dist/trpc/routers/clients.d.ts +21 -0
  159. package/dist/trpc/routers/clients.js +65 -0
  160. package/dist/trpc/routers/clients.js.map +1 -0
  161. package/dist/trpc/routers/health.d.ts +17 -0
  162. package/dist/trpc/routers/health.js +24 -0
  163. package/dist/trpc/routers/health.js.map +1 -0
  164. package/dist/trpc/routers/index.d.ts +394 -0
  165. package/dist/trpc/routers/index.js +23 -0
  166. package/dist/trpc/routers/index.js.map +1 -0
  167. package/dist/trpc/routers/indexers.d.ts +75 -0
  168. package/dist/trpc/routers/indexers.js +79 -0
  169. package/dist/trpc/routers/indexers.js.map +1 -0
  170. package/dist/trpc/routers/jobs.d.ts +33 -0
  171. package/dist/trpc/routers/jobs.js +84 -0
  172. package/dist/trpc/routers/jobs.js.map +1 -0
  173. package/dist/trpc/routers/logs.d.ts +27 -0
  174. package/dist/trpc/routers/logs.js +91 -0
  175. package/dist/trpc/routers/logs.js.map +1 -0
  176. package/dist/trpc/routers/searchees.d.ts +51 -0
  177. package/dist/trpc/routers/searchees.js +156 -0
  178. package/dist/trpc/routers/searchees.js.map +1 -0
  179. package/dist/trpc/routers/settings.d.ts +83 -0
  180. package/dist/trpc/routers/settings.js +92 -0
  181. package/dist/trpc/routers/settings.js.map +1 -0
  182. package/dist/trpc/routers/stats.d.ts +42 -0
  183. package/dist/trpc/routers/stats.js +102 -0
  184. package/dist/trpc/routers/stats.js.map +1 -0
  185. package/dist/userAuth.d.ts +21 -0
  186. package/dist/userAuth.js +86 -0
  187. package/dist/userAuth.js.map +1 -0
  188. package/dist/utils/authUtils.d.ts +10 -0
  189. package/dist/utils/authUtils.js +24 -0
  190. package/dist/utils/authUtils.js.map +1 -0
  191. package/dist/utils/logWatcher.d.ts +28 -0
  192. package/dist/utils/logWatcher.js +229 -0
  193. package/dist/utils/logWatcher.js.map +1 -0
  194. package/dist/utils/object.d.ts +1 -0
  195. package/dist/utils/object.js +4 -0
  196. package/dist/utils/object.js.map +1 -0
  197. package/dist/utils.d.ts +172 -0
  198. package/dist/utils.js +61 -50
  199. package/dist/utils.js.map +1 -1
  200. package/dist/webui/assets/FieldInfo-sRlPRNSK.js +1 -0
  201. package/dist/webui/assets/Page-B68mlTwU.js +1 -0
  202. package/dist/webui/assets/array-field-BCFMrvoU.js +1 -0
  203. package/dist/webui/assets/badge-C5YCxEzP.js +1 -0
  204. package/dist/webui/assets/check-NQsw6yBl.js +1 -0
  205. package/dist/webui/assets/chevron-down-8PGvFYxV.js +1 -0
  206. package/dist/webui/assets/clients-DnVpwApe.js +1 -0
  207. package/dist/webui/assets/connect-wMg2zyz6.js +1 -0
  208. package/dist/webui/assets/debug-BrjwiEi2.js +1 -0
  209. package/dist/webui/assets/directories-CHpJCWNR.js +1 -0
  210. package/dist/webui/assets/duration-field-DIkKt3iw.js +1 -0
  211. package/dist/webui/assets/general-uZrUIxbI.js +1 -0
  212. package/dist/webui/assets/health-_MuvAyjo.js +1 -0
  213. package/dist/webui/assets/index-B41DM2T5.css +1 -0
  214. package/dist/webui/assets/index-BBzHsn7u.js +1 -0
  215. package/dist/webui/assets/index-Ncy0-Qo7.js +54 -0
  216. package/dist/webui/assets/index-pKWy6v1P.js +1 -0
  217. package/dist/webui/assets/jobs-B8eat0YU.js +1 -0
  218. package/dist/webui/assets/library-BB0jQ8zn.js +1 -0
  219. package/dist/webui/assets/loader-circle-Bz67bJa3.js +1 -0
  220. package/dist/webui/assets/logs-CeP28848.js +1 -0
  221. package/dist/webui/assets/search-BRBIrqaX.js +1 -0
  222. package/dist/webui/assets/select-GZr6C6eZ.js +1 -0
  223. package/dist/webui/assets/select-field-CvT0SYk8.js +1 -0
  224. package/dist/webui/assets/settings-0ZdYY8g_.js +1 -0
  225. package/dist/webui/assets/submit-button-D7DKHqAq.js +1 -0
  226. package/dist/webui/assets/switch-BeMrf8sh.js +1 -0
  227. package/dist/webui/assets/switch-field-qMXHRKhx.js +1 -0
  228. package/dist/webui/assets/table-qEFWauuw.js +1 -0
  229. package/dist/webui/assets/test-tube-DhD6uWdp.js +1 -0
  230. package/dist/webui/assets/text-field-ZnKHDUks.js +1 -0
  231. package/dist/webui/assets/time-BM9K_Fbp.js +1 -0
  232. package/dist/webui/assets/trackers-BjJuAdX3.js +7 -0
  233. package/dist/webui/assets/use-form-validation-context-D2oA54L_.js +1 -0
  234. package/dist/webui/assets/use-settings-form-submit-CXwtE1sI.js +2 -0
  235. package/dist/webui/assets/useQuery-DD10sbzn.js +1 -0
  236. package/dist/webui/index.html +13 -0
  237. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts +261 -0
  238. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts.map +1 -0
  239. package/node_modules/@cross-seed/shared/dist/configSchema.js +53 -0
  240. package/node_modules/@cross-seed/shared/dist/configSchema.js.map +1 -0
  241. package/node_modules/@cross-seed/shared/dist/constants.d.ts +122 -0
  242. package/node_modules/@cross-seed/shared/dist/constants.d.ts.map +1 -0
  243. package/node_modules/@cross-seed/shared/dist/constants.js +127 -0
  244. package/node_modules/@cross-seed/shared/dist/constants.js.map +1 -0
  245. package/node_modules/@cross-seed/shared/dist/tsconfig.tsbuildinfo +1 -0
  246. package/node_modules/@cross-seed/shared/dist/utils.d.ts +9 -0
  247. package/node_modules/@cross-seed/shared/dist/utils.d.ts.map +1 -0
  248. package/node_modules/@cross-seed/shared/dist/utils.js +20 -0
  249. package/node_modules/@cross-seed/shared/dist/utils.js.map +1 -0
  250. package/node_modules/@cross-seed/shared/package.json +24 -0
  251. package/package.json +25 -42
  252. package/LICENSE +0 -201
  253. package/README.md +0 -34
  254. package/dist/config.template.cjs +0 -353
  255. package/dist/config.template.cjs.map +0 -1
@@ -0,0 +1,287 @@
1
+ import { z } from "zod";
2
+ import { db } from "../db.js";
3
+ import { Label, logger } from "../logger.js";
4
+ import { assembleUrl } from "../torznab.js";
5
+ import { USER_AGENT } from "../constants.js";
6
+ import { getAllIndexers } from "../indexers.js";
7
+ import { resultOf, resultOfErr } from "../Result.js";
8
+ import ms from "ms";
9
+ // Helper function to deserialize raw database row (snake_case columns) to Indexer
10
+ function deserializeRawRow(rawRow) {
11
+ const row = rawRow;
12
+ return {
13
+ id: row.id,
14
+ name: row.name,
15
+ url: row.url,
16
+ apikey: row.apikey,
17
+ enabled: Boolean(row.enabled),
18
+ status: row.status,
19
+ retryAfter: row.retry_after,
20
+ searchCap: Boolean(row.search_cap),
21
+ tvSearchCap: Boolean(row.tv_search_cap),
22
+ movieSearchCap: Boolean(row.movie_search_cap),
23
+ musicSearchCap: Boolean(row.music_search_cap),
24
+ audioSearchCap: Boolean(row.audio_search_cap),
25
+ bookSearchCap: Boolean(row.book_search_cap),
26
+ tvIdCaps: row.tv_id_caps ? JSON.parse(row.tv_id_caps) : null,
27
+ movieIdCaps: row.movie_id_caps
28
+ ? JSON.parse(row.movie_id_caps)
29
+ : null,
30
+ categories: row.cat_caps ? JSON.parse(row.cat_caps) : null,
31
+ limits: row.limits_caps ? JSON.parse(row.limits_caps) : null,
32
+ };
33
+ }
34
+ // Validation schemas
35
+ export const indexerCreateSchema = z.object({
36
+ name: z.string().min(1).optional(),
37
+ url: z.string().url(),
38
+ apikey: z.string().min(1),
39
+ enabled: z.boolean().default(true),
40
+ });
41
+ export const indexerUpdateSchema = z.object({
42
+ id: z.number().int().positive(),
43
+ name: z.string().min(1).optional().nullable(),
44
+ url: z.string().url().optional(),
45
+ apikey: z.string().min(1).optional(),
46
+ enabled: z.boolean().optional(),
47
+ });
48
+ export const indexerTestSchema = z.object({
49
+ url: z.string().url(),
50
+ apikey: z.string().min(1),
51
+ });
52
+ export async function testIndexerConnection(url, apikey, name) {
53
+ try {
54
+ const response = await fetch(assembleUrl(url, apikey, { t: "caps" }), {
55
+ headers: { "User-Agent": USER_AGENT },
56
+ signal: AbortSignal.timeout(ms("5 seconds")),
57
+ });
58
+ if (!response.ok) {
59
+ if (response.status === 401) {
60
+ return resultOfErr({
61
+ code: "AUTH_FAILED",
62
+ message: "Authentication failed - check API key",
63
+ });
64
+ }
65
+ else if (response.status === 429) {
66
+ return resultOfErr({
67
+ code: "RATE_LIMITED",
68
+ message: "Rate limited by indexer",
69
+ });
70
+ }
71
+ else {
72
+ return resultOfErr({
73
+ code: "CONNECTION_FAILED",
74
+ message: "Connection failed",
75
+ });
76
+ }
77
+ }
78
+ logger.verbose({
79
+ label: Label.TORZNAB,
80
+ message: `Test connection successful for: ${name}`,
81
+ });
82
+ return resultOf({
83
+ success: true,
84
+ message: "Connection successful",
85
+ });
86
+ }
87
+ catch (error) {
88
+ const message = error.message;
89
+ logger.warn({
90
+ label: Label.TORZNAB,
91
+ message: `Test connection failed for ${name}: ${message}`,
92
+ });
93
+ // Handle timeout specifically - AbortSignal.timeout() throws TimeoutError (DOMException)
94
+ if (error.name === "TimeoutError" || error.name === "AbortError") {
95
+ return resultOfErr({
96
+ code: "TIMEOUT",
97
+ message: "Connection timed out",
98
+ });
99
+ }
100
+ return resultOfErr({
101
+ code: "CONNECTION_FAILED",
102
+ message: "Connection failed",
103
+ });
104
+ }
105
+ }
106
+ export async function createIndexer(input) {
107
+ // Create new indexer only (no upsert)
108
+ const [result] = await db("indexer")
109
+ .insert({
110
+ name: input.name || null,
111
+ url: input.url,
112
+ apikey: input.apikey,
113
+ trackers: null,
114
+ enabled: input.enabled,
115
+ status: null,
116
+ retry_after: null,
117
+ search_cap: null,
118
+ tv_search_cap: null,
119
+ movie_search_cap: null,
120
+ music_search_cap: null,
121
+ audio_search_cap: null,
122
+ book_search_cap: null,
123
+ tv_id_caps: null,
124
+ movie_id_caps: null,
125
+ cat_caps: null,
126
+ limits_caps: null,
127
+ })
128
+ .returning("id");
129
+ // Query the created record
130
+ const rawRow = await db("indexer").where({ id: result.id }).first();
131
+ const indexer = deserializeRawRow(rawRow);
132
+ logger.verbose({
133
+ label: Label.TORZNAB,
134
+ message: `Created indexer: ${input.name || input.url}`,
135
+ });
136
+ return indexer;
137
+ }
138
+ export async function updateIndexer(input) {
139
+ const { id, ...updates } = input;
140
+ // Prepare update object
141
+ const updateData = {
142
+ ...(updates.name !== undefined && { name: updates.name }),
143
+ ...(updates.url !== undefined && { url: updates.url }),
144
+ ...(updates.apikey !== undefined && { apikey: updates.apikey }),
145
+ ...(updates.enabled !== undefined && { enabled: updates.enabled }),
146
+ };
147
+ // Atomic update with existence check
148
+ const updateCount = await db("indexer").where({ id }).update(updateData);
149
+ if (updateCount === 0) {
150
+ return resultOfErr({
151
+ code: "INDEXER_NOT_FOUND",
152
+ message: `Indexer with ID ${id} not found`,
153
+ });
154
+ }
155
+ // Query the updated record
156
+ const updatedRawRow = await db("indexer").where({ id }).first();
157
+ const updatedIndexer = deserializeRawRow(updatedRawRow);
158
+ logger.verbose({
159
+ label: Label.TORZNAB,
160
+ message: `Updated indexer: ${updatedIndexer.name || updatedIndexer.url}`,
161
+ });
162
+ return resultOf(updatedIndexer);
163
+ }
164
+ export async function deleteIndexer(id) {
165
+ return db.transaction(async (trx) => {
166
+ const rawRow = await trx("indexer").where({ id }).first();
167
+ if (!rawRow) {
168
+ return resultOfErr({
169
+ code: "INDEXER_NOT_FOUND",
170
+ message: `Indexer with ID ${id} not found`,
171
+ });
172
+ }
173
+ const indexer = deserializeRawRow(rawRow);
174
+ await trx("timestamp").where({ indexer_id: id }).del();
175
+ await trx("rss").where({ indexer_id: id }).del();
176
+ await trx("indexer").where({ id }).del();
177
+ logger.verbose({
178
+ label: Label.TORZNAB,
179
+ message: `Deleted indexer: ${indexer.name || indexer.url}`,
180
+ });
181
+ return resultOf({ success: true, indexer });
182
+ });
183
+ }
184
+ export async function getIndexerById(id) {
185
+ const rawRow = await db("indexer").where({ id }).first();
186
+ if (!rawRow) {
187
+ return resultOfErr({
188
+ code: "INDEXER_NOT_FOUND",
189
+ message: `Indexer with ID ${id} not found`,
190
+ });
191
+ }
192
+ return resultOf(deserializeRawRow(rawRow));
193
+ }
194
+ // TODO: This is a thin wrapper around getAllIndexers from ../indexers.js
195
+ // Consider consolidating duplicate functionality between indexerService and indexers modules
196
+ export async function listAllIndexers() {
197
+ return getAllIndexers();
198
+ }
199
+ export async function mergeDisabledIndexer(sourceId, targetId) {
200
+ if (sourceId === targetId) {
201
+ return resultOfErr({
202
+ code: "SAME_INDEXER",
203
+ message: "Source and target indexers must be different",
204
+ });
205
+ }
206
+ return db.transaction(async (trx) => {
207
+ const sourceIndexer = await trx("indexer")
208
+ .select("id", "enabled")
209
+ .where({ id: sourceId })
210
+ .first();
211
+ if (!sourceIndexer) {
212
+ return resultOfErr({
213
+ code: "SOURCE_NOT_FOUND",
214
+ message: `Disabled indexer with ID ${sourceId} not found`,
215
+ });
216
+ }
217
+ if (sourceIndexer.enabled) {
218
+ return resultOfErr({
219
+ code: "INVALID_SOURCE_STATE",
220
+ message: "Source indexer must be disabled before merging",
221
+ });
222
+ }
223
+ const targetIndexer = await trx("indexer")
224
+ .select("id", "enabled")
225
+ .where({ id: targetId })
226
+ .first();
227
+ if (!targetIndexer) {
228
+ return resultOfErr({
229
+ code: "TARGET_NOT_FOUND",
230
+ message: `Target indexer with ID ${targetId} not found`,
231
+ });
232
+ }
233
+ if (!targetIndexer.enabled) {
234
+ return resultOfErr({
235
+ code: "INVALID_TARGET_STATE",
236
+ message: "Target indexer must be enabled",
237
+ });
238
+ }
239
+ const sourceTimestamps = await trx("timestamp")
240
+ .where({ indexer_id: sourceId })
241
+ .select("searchee_id as searcheeId", "first_searched as firstSearched", "last_searched as lastSearched");
242
+ if (sourceTimestamps.length > 0) {
243
+ const rows = sourceTimestamps.map((row) => ({
244
+ indexer_id: targetId,
245
+ searchee_id: row.searcheeId,
246
+ first_searched: row.firstSearched,
247
+ last_searched: row.lastSearched,
248
+ }));
249
+ await trx("timestamp")
250
+ .insert(rows)
251
+ .onConflict(["searchee_id", "indexer_id"])
252
+ .merge({
253
+ first_searched: trx.raw("MIN(timestamp.first_searched, excluded.first_searched)"),
254
+ last_searched: trx.raw("MAX(timestamp.last_searched, excluded.last_searched)"),
255
+ });
256
+ await trx("timestamp").where({ indexer_id: sourceId }).del();
257
+ logger.verbose({
258
+ label: Label.TORZNAB,
259
+ message: `Merged ${sourceTimestamps.length} timestamp rows from indexer ${sourceId} into ${targetId}`,
260
+ });
261
+ }
262
+ await trx("rss").where({ indexer_id: sourceId }).del();
263
+ const deleted = await trx("indexer").where({ id: sourceId }).del();
264
+ if (deleted) {
265
+ logger.verbose({
266
+ label: Label.TORZNAB,
267
+ message: `Deleted disabled indexer ${sourceId} after merge into ${targetId}`,
268
+ });
269
+ }
270
+ return resultOf({
271
+ mergedCount: sourceTimestamps.length,
272
+ deleted: Boolean(deleted),
273
+ });
274
+ });
275
+ }
276
+ export async function testExistingIndexer(id) {
277
+ const indexerResult = await getIndexerById(id);
278
+ if (indexerResult.isErr()) {
279
+ return indexerResult;
280
+ }
281
+ const indexer = indexerResult.unwrap();
282
+ return testIndexerConnection(indexer.url, indexer.apikey, indexer.name || indexer.url);
283
+ }
284
+ export async function testNewIndexer(input) {
285
+ return testIndexerConnection(input.url, input.apikey, input.url);
286
+ }
287
+ //# sourceMappingURL=indexerService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexerService.js","sourceRoot":"","sources":["../../src/services/indexerService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { FastifyReply, FastifyRequest } from "fastify";
2
+ export declare function setSessionCookie(reply: FastifyReply, sessionId: string): void;
3
+ export declare function clearSessionCookie(reply: FastifyReply): void;
4
+ export declare function getSessionCookie(request: FastifyRequest): string | undefined;
5
+ export declare function validateAuth(request: FastifyRequest): Promise<boolean>;
@@ -0,0 +1,27 @@
1
+ import { validateSession } from "./userAuth.js";
2
+ const COOKIE_NAME = "cross-seed-session";
3
+ const COOKIE_OPTIONS = {
4
+ httpOnly: true,
5
+ secure: process.env.NODE_ENV === "production", // Only set secure in production
6
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days in milliseconds (match session expiry)
7
+ path: "/",
8
+ sameSite: "lax",
9
+ };
10
+ export function setSessionCookie(reply, sessionId) {
11
+ void reply.setCookie(COOKIE_NAME, sessionId, COOKIE_OPTIONS);
12
+ }
13
+ export function clearSessionCookie(reply) {
14
+ void reply.clearCookie(COOKIE_NAME, { path: "/" });
15
+ }
16
+ export function getSessionCookie(request) {
17
+ return request.cookies[COOKIE_NAME];
18
+ }
19
+ export async function validateAuth(request) {
20
+ const sessionId = getSessionCookie(request);
21
+ if (!sessionId) {
22
+ return false;
23
+ }
24
+ const user = await validateSession(sessionId);
25
+ return !!user;
26
+ }
27
+ //# sourceMappingURL=sessionCookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionCookies.js","sourceRoot":"","sources":["../src/sessionCookies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,cAAc,GAAG;IACtB,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,gCAAgC;IAC/E,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,iDAAiD;IACnF,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,KAAc;CACxB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,KAAmB,EAAE,SAAiB;IACtE,KAAK,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAmB;IACrD,KAAK,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAuB;IACvD,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAuB;IACzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE5C,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IAC9C,OAAO,CAAC,CAAC,IAAI,CAAC;AACf,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { RuntimeConfig } from "./runtimeConfig.js";
2
+ import { Awaitable } from "./utils.js";
3
+ export declare function exitGracefully(): Promise<void>;
4
+ /**
5
+ * validates and sets RuntimeConfig
6
+ * @return (the number of errors Zod encountered in the configuration)
7
+ */
8
+ /**
9
+ * starts singletons, then runs the callback, then cleans up
10
+ * @param entrypoint
11
+ */
12
+ type CommanderActionCb = (options: Record<string, unknown>) => void | Promise<void>;
13
+ /**
14
+ * Initializes only the database, runs the callback, then cleans up
15
+ */
16
+ export declare function withMinimalRuntime<T extends (...args: unknown[]) => Awaitable<string | void>>(entrypoint: T, { migrate }?: {
17
+ migrate?: boolean | undefined;
18
+ }): (...args: Parameters<T>) => Promise<void>;
19
+ /**
20
+ * Initializes the full runtime, runs the callback, then cleans up
21
+ * @param entrypoint
22
+ */
23
+ export declare function withFullRuntime(entrypoint: (runtimeConfig: RuntimeConfig) => Promise<void>): CommanderActionCb;
24
+ export declare function restartCrossSeed(): Promise<void>;
25
+ export {};
package/dist/startup.js CHANGED
@@ -1,170 +1,47 @@
1
- import { constants, mkdir, stat } from "fs/promises";
2
- import ms from "ms";
3
- import { inspect } from "util";
4
- import { testLinking } from "./action.js";
5
- import { validateUArrLs } from "./arr.js";
6
- import { getClients, instantiateDownloadClients, } from "./clients/TorrentClient.js";
7
- import { customizeErrorMessage, VALIDATION_SCHEMA } from "./configSchema.js";
8
- import { NEWLINE_INDENT, PROGRAM_NAME, PROGRAM_VERSION } from "./constants.js";
1
+ import { mkdir } from "fs/promises";
2
+ import { spawn } from "node:child_process";
3
+ import { resetApiKey } from "./auth.js";
4
+ import { instantiateDownloadClients } from "./clients/TorrentClient.js";
5
+ import { createAppDirHierarchy, getDefaultRuntimeConfig, getFileConfig, stripDefaults, transformFileConfig, } from "./configuration.js";
9
6
  import { db } from "./db.js";
10
- import { CrossSeedError, exitOnCrossSeedErrors } from "./errors.js";
11
- import { initializeLogger, Label, logger } from "./logger.js";
7
+ import { getDbConfig, setDbConfig } from "./dbConfig.js";
8
+ import { exitOnCrossSeedErrors, initializeLogger, Label, logger, } from "./logger.js";
12
9
  import { initializePushNotifier } from "./pushNotifier.js";
13
10
  import { getRuntimeConfig, setRuntimeConfig, } from "./runtimeConfig.js";
14
- import { validateTorznabUrls } from "./torznab.js";
15
- import { mapAsync, notExists, verifyDir, wait } from "./utils.js";
11
+ import { notExists } from "./utils.js";
12
+ import { getLogWatcher } from "./utils/logWatcher.js";
13
+ import { omitUndefined } from "./utils/object.js";
16
14
  export async function exitGracefully() {
17
15
  await db.destroy();
18
16
  process.exit();
19
17
  }
20
18
  process.on("SIGINT", exitGracefully);
21
19
  process.on("SIGTERM", exitGracefully);
22
- /**
23
- * verifies the config paths provided against the filesystem
24
- * @returns true (if paths are valid)
25
- */
26
- async function checkConfigPaths() {
27
- const { dataDirs, injectDir, linkDirs, outputDir, torrentDir } = getRuntimeConfig();
28
- const READ_ONLY = constants.R_OK;
29
- const READ_AND_WRITE = constants.R_OK | constants.W_OK;
30
- let pathFailure = 0;
31
- const linkDev = [];
32
- const dataDev = [];
33
- if (torrentDir && !(await verifyDir(torrentDir, "torrentDir", READ_ONLY))) {
34
- pathFailure++;
35
- }
36
- if (await notExists(outputDir)) {
37
- logger.info(`Creating outputDir: ${outputDir}`);
38
- await mkdir(outputDir, { recursive: true });
39
- }
40
- if (!(await verifyDir(outputDir, "outputDir", READ_AND_WRITE))) {
41
- pathFailure++;
20
+ async function ensureConfiguredDirectories() {
21
+ const { outputDir, linkDirs = [] } = getRuntimeConfig();
22
+ const directories = [];
23
+ if (outputDir) {
24
+ directories.push({ path: outputDir, label: "outputDir" });
42
25
  }
43
26
  for (const [index, linkDir] of linkDirs.entries()) {
44
- const linkDirName = `linkDir${index}`;
45
- if (await notExists(linkDir)) {
46
- logger.info(`Creating ${linkDirName}: ${linkDir}`);
47
- await mkdir(linkDir, { recursive: true });
48
- }
49
- if (await verifyDir(linkDir, linkDirName, READ_AND_WRITE)) {
50
- linkDev.push({ path: linkDir, dev: (await stat(linkDir)).dev });
51
- }
52
- else {
53
- pathFailure++;
54
- }
27
+ directories.push({ path: linkDir, label: `linkDir${index}` });
55
28
  }
56
- if (linkDev.length) {
57
- logger.verbose(`Storage device for each linkDir: ${inspect(linkDev)}`);
58
- }
59
- for (const [index, dataDir] of dataDirs.entries()) {
60
- const dataDirName = `dataDir${index}`;
61
- if (await verifyDir(dataDir, dataDirName, READ_ONLY)) {
62
- dataDev.push({ path: dataDir, dev: (await stat(dataDir)).dev });
63
- }
64
- else {
65
- pathFailure++;
66
- }
67
- }
68
- if (dataDev.length) {
69
- logger.verbose(`Storage device for each dataDir: ${inspect(dataDev)}`);
70
- }
71
- if (injectDir) {
72
- if (!(await verifyDir(injectDir, "injectDir", READ_AND_WRITE))) {
73
- pathFailure++;
29
+ for (const { path, label } of directories) {
30
+ if (!(await notExists(path))) {
31
+ continue;
74
32
  }
75
- }
76
- if (linkDirs.length) {
77
- for (const [index, dataDir] of dataDirs.entries()) {
78
- const dataDirName = `dataDir${index}`;
79
- try {
80
- const res = await testLinking(dataDir, `${dataDirName}Src.cross-seed`, `${dataDirName}Dest.cross-seed`);
81
- if (!res) {
82
- logger.error("Failed to link from dataDirs to linkDirs.");
83
- }
84
- }
85
- catch (e) {
86
- logger.error(e);
87
- logger.error("Failed to link from dataDirs to linkDirs.");
88
- pathFailure++;
89
- }
90
- }
91
- }
92
- if (pathFailure) {
93
- throw new CrossSeedError(`\tYour configuration is invalid, please see the ${pathFailure > 1 ? "errors" : "error"} above for details.`);
94
- }
95
- }
96
- async function retry(cb, numRetries, delayMs) {
97
- const retries = Math.max(numRetries, 0);
98
- let lastError = new Error("Retry failed");
99
- for (let i = 0; i <= retries; i++) {
100
33
  try {
101
- return await cb();
102
- }
103
- catch (e) {
104
- const retryMsg = i < retries ? `, retrying in ${delayMs / 1000}s` : "";
105
- logger.error(`Attempt ${i + 1}/${retries + 1} failed${retryMsg}: ${e.message}`);
106
- logger.debug(e);
107
- lastError = e;
108
- if (i >= retries)
109
- break;
110
- await wait(delayMs);
34
+ logger.info(`Creating ${label}: ${path}`);
35
+ await mkdir(path, { recursive: true });
111
36
  }
112
- }
113
- throw lastError;
114
- }
115
- export async function doStartupValidation() {
116
- await checkConfigPaths(); // ensure paths are valid first
117
- instantiateDownloadClients();
118
- const validateClientConfig = () => mapAsync(getClients(), (client) => client.validateConfig());
119
- const errors = (await Promise.allSettled([
120
- retry(validateTorznabUrls, 5, ms("1 minute")),
121
- retry(validateUArrLs, 5, ms("1 minute")),
122
- retry(validateClientConfig, 5, ms("1 minute")),
123
- ])).filter((p) => p.status === "rejected");
124
- if (errors.length) {
125
- throw new CrossSeedError(`\tYour configuration is invalid, please see the ${errors.length > 1 ? "errors" : "error"} above for details.`);
126
- }
127
- logger.verbose({
128
- label: Label.CONFIGDUMP,
129
- message: inspect(getRuntimeConfig()),
130
- });
131
- logger.info("Your configuration is valid!");
132
- }
133
- /**
134
- * validates and sets RuntimeConfig
135
- * @return (the number of errors Zod encountered in the configuration)
136
- */
137
- export function parseRuntimeConfigAndLogErrors(options) {
138
- logger.info(`${PROGRAM_NAME} v${PROGRAM_VERSION}`);
139
- logger.info("Validating your configuration...");
140
- let parsedOptions;
141
- try {
142
- parsedOptions = VALIDATION_SCHEMA.parse(options, {
143
- errorMap: customizeErrorMessage,
144
- });
145
- }
146
- catch (error) {
147
- logger.verbose({
148
- label: Label.CONFIGDUMP,
149
- message: inspect(options),
150
- });
151
- if ("errors" in error && Array.isArray(error.errors)) {
152
- error.errors.forEach(({ path, message }) => {
153
- const urlPath = path[0];
154
- const optionLine = path.length === 2
155
- ? `${path[0]} (position #${path[1] + 1})`
156
- : path;
157
- logger.error(`${path.length > 0
158
- ? `Option: ${optionLine}`
159
- : "Configuration:"}${NEWLINE_INDENT}${message}${NEWLINE_INDENT}(https://www.cross-seed.org/docs/basics/options${urlPath ? `#${urlPath.toLowerCase()}` : ""})\n`);
37
+ catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error ?? "");
39
+ logger.error({
40
+ label: Label.SERVER,
41
+ message: `Failed to create ${label} at ${path}: ${message}`,
160
42
  });
161
- if (error.errors.length > 0) {
162
- throw new CrossSeedError(`Your configuration is invalid, please see the ${error.errors.length > 1 ? "errors" : "error"} above for details.`);
163
- }
164
43
  }
165
- throw error;
166
44
  }
167
- return parsedOptions;
168
45
  }
169
46
  /**
170
47
  * Initializes only the database, runs the callback, then cleans up
@@ -186,18 +63,95 @@ export function withMinimalRuntime(entrypoint, { migrate = true } = {}) {
186
63
  }
187
64
  };
188
65
  }
66
+ async function applyExistingApiKey(config) {
67
+ try {
68
+ const existingApiKey = await db("settings").select("apikey").first();
69
+ if (existingApiKey?.apikey && !config.apiKey) {
70
+ config.apiKey = existingApiKey.apikey;
71
+ }
72
+ }
73
+ catch (error) {
74
+ // best-effort only
75
+ }
76
+ }
77
+ async function determineRuntimeConfig(rawOptions) {
78
+ const cliOptions = omitUndefined(rawOptions);
79
+ // first, try to load from database (existing user happy path)
80
+ let dbOverrides;
81
+ try {
82
+ dbOverrides = await getDbConfig();
83
+ }
84
+ catch (dbError) {
85
+ logger.debug("Unable to load configuration from database", dbError);
86
+ }
87
+ if (dbOverrides !== undefined) {
88
+ return {
89
+ ...getDefaultRuntimeConfig(),
90
+ ...dbOverrides,
91
+ ...cliOptions,
92
+ };
93
+ }
94
+ // then, try to migrate from file config (v6 to v7 upgrade path)
95
+ try {
96
+ const fileConfig = await getFileConfig();
97
+ if (fileConfig) {
98
+ const transformedFileConfig = transformFileConfig(fileConfig);
99
+ const runtimeFromFile = {
100
+ ...getDefaultRuntimeConfig(),
101
+ ...transformedFileConfig,
102
+ };
103
+ await applyExistingApiKey(runtimeFromFile);
104
+ await setDbConfig(runtimeFromFile);
105
+ const resolvedOverrides = stripDefaults(runtimeFromFile);
106
+ logger.info("Migrated file config to database");
107
+ return {
108
+ ...getDefaultRuntimeConfig(),
109
+ ...resolvedOverrides,
110
+ ...cliOptions,
111
+ };
112
+ }
113
+ }
114
+ catch (migrationError) {
115
+ logger.error(new Error("Failed to import configuration file, falling back to defaults", { cause: migrationError }));
116
+ }
117
+ // finally, fall back to defaults (new user happy path or migration failure)
118
+ const defaultRuntime = getDefaultRuntimeConfig();
119
+ await setDbConfig(defaultRuntime);
120
+ await resetApiKey();
121
+ logger.info("Created initial database config from defaults");
122
+ const resolvedOverrides = stripDefaults(defaultRuntime);
123
+ return {
124
+ ...getDefaultRuntimeConfig(),
125
+ ...resolvedOverrides,
126
+ ...cliOptions,
127
+ };
128
+ }
189
129
  /**
190
130
  * Initializes the full runtime, runs the callback, then cleans up
191
131
  * @param entrypoint
192
132
  */
193
133
  export function withFullRuntime(entrypoint) {
194
134
  return withMinimalRuntime(async (options) => {
135
+ createAppDirHierarchy();
195
136
  initializeLogger(options);
196
- const runtimeConfig = parseRuntimeConfigAndLogErrors(options);
137
+ const runtimeConfig = await determineRuntimeConfig(options);
197
138
  setRuntimeConfig(runtimeConfig);
198
139
  initializePushNotifier();
199
- await doStartupValidation();
140
+ getLogWatcher();
141
+ await ensureConfiguredDirectories();
142
+ instantiateDownloadClients();
200
143
  await entrypoint(runtimeConfig);
201
144
  });
202
145
  }
146
+ export async function restartCrossSeed() {
147
+ logger.info("Restarting cross-seed");
148
+ process.on("exit", () => {
149
+ spawn(process.argv[0], process.argv.slice(1), {
150
+ cwd: process.cwd(),
151
+ stdio: "inherit",
152
+ detached: false,
153
+ });
154
+ });
155
+ await exitGracefully();
156
+ }
203
157
  //# sourceMappingURL=startup.js.map