@valentinkolb/filegate 2.4.0 → 2.5.2

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 (88) hide show
  1. package/dist/activity.d.ts +15 -0
  2. package/dist/activity.d.ts.map +1 -0
  3. package/dist/activity.js +21 -0
  4. package/dist/activity.js.map +1 -0
  5. package/dist/capabilities.d.ts +9 -0
  6. package/dist/capabilities.d.ts.map +1 -0
  7. package/dist/capabilities.js +11 -0
  8. package/dist/capabilities.js.map +1 -0
  9. package/dist/client.d.ts +37 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +77 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/core.d.ts +26 -0
  14. package/dist/core.d.ts.map +1 -0
  15. package/dist/core.js +58 -0
  16. package/dist/core.js.map +1 -0
  17. package/dist/downloads.d.ts +9 -0
  18. package/dist/downloads.d.ts.map +1 -0
  19. package/dist/downloads.js +11 -0
  20. package/dist/downloads.js.map +1 -0
  21. package/dist/errors.d.ts +18 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +42 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/index-client.d.ts +17 -0
  26. package/dist/index-client.d.ts.map +1 -0
  27. package/dist/index-client.js +29 -0
  28. package/dist/index-client.js.map +1 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +7 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/nodes.d.ts +27 -0
  34. package/dist/nodes.d.ts.map +1 -0
  35. package/dist/nodes.js +71 -0
  36. package/dist/nodes.js.map +1 -0
  37. package/dist/paths.d.ts +45 -0
  38. package/dist/paths.d.ts.map +1 -0
  39. package/dist/paths.js +71 -0
  40. package/dist/paths.js.map +1 -0
  41. package/dist/search.d.ts +17 -0
  42. package/dist/search.d.ts.map +1 -0
  43. package/dist/search.js +25 -0
  44. package/dist/search.js.map +1 -0
  45. package/dist/stats.d.ts +9 -0
  46. package/dist/stats.d.ts.map +1 -0
  47. package/dist/stats.js +11 -0
  48. package/dist/stats.js.map +1 -0
  49. package/dist/transfers.d.ts +9 -0
  50. package/dist/transfers.d.ts.map +1 -0
  51. package/dist/transfers.js +14 -0
  52. package/dist/transfers.js.map +1 -0
  53. package/dist/types.d.ts +285 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +2 -0
  56. package/dist/types.js.map +1 -0
  57. package/dist/uploads.d.ts +246 -0
  58. package/dist/uploads.d.ts.map +1 -0
  59. package/dist/uploads.js +580 -0
  60. package/dist/uploads.js.map +1 -0
  61. package/dist/utils.d.ts +25 -0
  62. package/dist/utils.d.ts.map +1 -0
  63. package/dist/utils.js +41 -0
  64. package/dist/utils.js.map +1 -0
  65. package/dist/versions.d.ts +76 -0
  66. package/dist/versions.d.ts.map +1 -0
  67. package/dist/versions.js +82 -0
  68. package/dist/versions.js.map +1 -0
  69. package/package.json +36 -41
  70. package/LICENSE +0 -21
  71. package/README.md +0 -569
  72. package/src/client.ts +0 -436
  73. package/src/config.ts +0 -41
  74. package/src/handlers/files.ts +0 -696
  75. package/src/handlers/indexHandler.ts +0 -107
  76. package/src/handlers/search.ts +0 -144
  77. package/src/handlers/thumbnail.ts +0 -174
  78. package/src/handlers/upload.ts +0 -401
  79. package/src/index.ts +0 -131
  80. package/src/lib/index.ts +0 -325
  81. package/src/lib/openapi.ts +0 -48
  82. package/src/lib/ownership.ts +0 -133
  83. package/src/lib/path.ts +0 -128
  84. package/src/lib/response.ts +0 -10
  85. package/src/lib/scanner.ts +0 -121
  86. package/src/lib/validator.ts +0 -21
  87. package/src/schemas.ts +0 -376
  88. package/src/utils.ts +0 -282
@@ -1,107 +0,0 @@
1
- import { Hono } from "hono";
2
- import { describeRoute } from "hono-openapi";
3
- import { join } from "node:path";
4
- import { jsonResponse, requiresAuth } from "../lib/openapi";
5
- import { v } from "../lib/validator";
6
- import {
7
- RescanResponseSchema,
8
- IndexStatsSchema,
9
- BulkResolveBodySchema,
10
- BulkResolveResponseSchema,
11
- ErrorSchema,
12
- } from "../schemas";
13
- import { config } from "../config";
14
- import { bulkResolve, getIndexStats } from "../lib/index";
15
- import { scanAll } from "../lib/scanner";
16
-
17
- const app = new Hono();
18
-
19
- app.post(
20
- "/rescan",
21
- describeRoute({
22
- tags: ["Index"],
23
- summary: "Rescan file index",
24
- ...requiresAuth,
25
- responses: {
26
- 200: jsonResponse(RescanResponseSchema, "Rescan completed"),
27
- 400: jsonResponse(ErrorSchema, "Bad request"),
28
- 500: jsonResponse(ErrorSchema, "Internal error"),
29
- },
30
- }),
31
- async (c) => {
32
- if (!config.indexEnabled) {
33
- return c.json({ error: "index disabled" }, 400);
34
- }
35
-
36
- try {
37
- const result = await scanAll();
38
- return c.json(result);
39
- } catch (err) {
40
- console.error("[Filegate] Index rescan failed:", err);
41
- return c.json({ error: "rescan failed" }, 500);
42
- }
43
- },
44
- );
45
-
46
- app.get(
47
- "/stats",
48
- describeRoute({
49
- tags: ["Index"],
50
- summary: "Get index stats",
51
- ...requiresAuth,
52
- responses: {
53
- 200: jsonResponse(IndexStatsSchema, "Index stats"),
54
- 400: jsonResponse(ErrorSchema, "Bad request"),
55
- 500: jsonResponse(ErrorSchema, "Internal error"),
56
- },
57
- }),
58
- async (c) => {
59
- if (!config.indexEnabled) {
60
- return c.json({ error: "index disabled" }, 400);
61
- }
62
-
63
- try {
64
- const stats = await getIndexStats();
65
- return c.json(stats);
66
- } catch (err) {
67
- console.error("[Filegate] Index stats failed:", err);
68
- return c.json({ error: "stats failed" }, 500);
69
- }
70
- },
71
- );
72
-
73
- app.post(
74
- "/resolve",
75
- describeRoute({
76
- tags: ["Index"],
77
- summary: "Resolve file IDs to paths",
78
- ...requiresAuth,
79
- responses: {
80
- 200: jsonResponse(BulkResolveResponseSchema, "Resolved paths"),
81
- 400: jsonResponse(ErrorSchema, "Bad request"),
82
- 500: jsonResponse(ErrorSchema, "Internal error"),
83
- },
84
- }),
85
- v("json", BulkResolveBodySchema),
86
- async (c) => {
87
- if (!config.indexEnabled) {
88
- return c.json({ error: "index disabled" }, 400);
89
- }
90
-
91
- try {
92
- const { ids } = c.req.valid("json");
93
- const resolved = await bulkResolve(ids);
94
- const response: Record<string, string | null> = {};
95
- for (const id of ids) {
96
- const entry = resolved[id];
97
- response[id] = entry ? join(entry.basePath, entry.relPath) : null;
98
- }
99
- return c.json(response);
100
- } catch (err) {
101
- console.error("[Filegate] Index resolve failed:", err);
102
- return c.json({ error: "resolve failed" }, 500);
103
- }
104
- },
105
- );
106
-
107
- export default app;
@@ -1,144 +0,0 @@
1
- import { Hono } from "hono";
2
- import { describeRoute } from "hono-openapi";
3
- import { Glob } from "bun";
4
- import { stat } from "node:fs/promises";
5
- import { join, basename, relative } from "node:path";
6
- import { validatePath } from "../lib/path";
7
- import { jsonResponse, requiresAuth } from "../lib/openapi";
8
- import { v } from "../lib/validator";
9
- import {
10
- SearchResponseSchema,
11
- ErrorSchema,
12
- SearchQuerySchema,
13
- countRecursiveWildcards,
14
- type FileInfo,
15
- type SearchResult,
16
- } from "../schemas";
17
- import { config } from "../config";
18
- import { enrichFileInfoBatch } from "../lib/index";
19
-
20
- const app = new Hono();
21
-
22
- const getFileInfo = async (fullPath: string, basePath: string): Promise<FileInfo | null> => {
23
- try {
24
- const s = await stat(fullPath);
25
- const name = basename(fullPath);
26
- const file = Bun.file(fullPath);
27
-
28
- return {
29
- name,
30
- path: relative(basePath, fullPath),
31
- type: s.isDirectory() ? "directory" : "file",
32
- size: s.isDirectory() ? 0 : s.size,
33
- mtime: s.mtime.toISOString(),
34
- isHidden: name.startsWith("."),
35
- mimeType: s.isDirectory() ? undefined : file.type,
36
- };
37
- } catch {
38
- return null;
39
- }
40
- };
41
-
42
- const searchInPath = async (
43
- basePath: string,
44
- pattern: string,
45
- showHidden: boolean,
46
- limit: number,
47
- includeFiles: boolean,
48
- includeDirectories: boolean,
49
- ): Promise<SearchResult> => {
50
- const glob = new Glob(pattern);
51
- const files: FileInfo[] = [];
52
- let hasMore = false;
53
-
54
- // If we need directories, we must set onlyFiles: false
55
- const onlyFiles = includeFiles && !includeDirectories;
56
-
57
- for await (const match of glob.scan({ cwd: basePath, dot: showHidden, onlyFiles })) {
58
- if (!showHidden && basename(match).startsWith(".")) continue;
59
-
60
- if (files.length >= limit) {
61
- hasMore = true;
62
- break;
63
- }
64
-
65
- const info = await getFileInfo(join(basePath, match), basePath);
66
- if (!info) continue;
67
-
68
- // Filter based on type
69
- if (info.type === "file" && !includeFiles) continue;
70
- if (info.type === "directory" && !includeDirectories) continue;
71
-
72
- files.push(info);
73
- }
74
-
75
- return { basePath, files, total: files.length, hasMore };
76
- };
77
-
78
- app.get(
79
- "/search",
80
- describeRoute({
81
- tags: ["Search"],
82
- summary: "Search files with glob pattern",
83
- description: "Search multiple paths in parallel using glob patterns",
84
- ...requiresAuth,
85
- responses: {
86
- 200: jsonResponse(SearchResponseSchema, "Search results"),
87
- 400: jsonResponse(ErrorSchema, "Bad request"),
88
- 403: jsonResponse(ErrorSchema, "Forbidden"),
89
- 404: jsonResponse(ErrorSchema, "Not found"),
90
- },
91
- }),
92
- v("query", SearchQuerySchema),
93
- async (c) => {
94
- const { paths: pathsParam, pattern, showHidden, limit: limitParam, files, directories } = c.req.valid("query");
95
-
96
- // At least one of files or directories must be true
97
- if (!files && !directories) {
98
- return c.json({ error: "at least one of 'files' or 'directories' must be true" }, 400);
99
- }
100
-
101
- // Validate recursive wildcard count
102
- const wildcardCount = countRecursiveWildcards(pattern);
103
- if (wildcardCount > config.searchMaxRecursiveWildcards) {
104
- return c.json(
105
- { error: `too many recursive wildcards: ${wildcardCount} (max ${config.searchMaxRecursiveWildcards})` },
106
- 400,
107
- );
108
- }
109
-
110
- const limit = Math.min(limitParam ?? config.searchMaxResults, config.searchMaxResults);
111
- const paths = pathsParam
112
- .split(",")
113
- .map((p) => p.trim())
114
- .filter(Boolean);
115
- const validPaths: string[] = [];
116
-
117
- for (const p of paths) {
118
- const result = await validatePath(p, { allowBasePath: true });
119
- if (!result.ok) return c.json({ error: result.error }, result.status);
120
-
121
- const s = await stat(result.realPath).catch(() => null);
122
- if (!s) return c.json({ error: `path not found: ${p}` }, 404);
123
- if (!s.isDirectory()) return c.json({ error: `not a directory: ${p}` }, 400);
124
-
125
- validPaths.push(result.realPath);
126
- }
127
-
128
- const results = await Promise.all(
129
- validPaths.map((p) => searchInPath(p, pattern, showHidden, limit, files, directories)),
130
- );
131
-
132
- if (config.indexEnabled) {
133
- for (const result of results) {
134
- result.files = await enrichFileInfoBatch(result.files, result.basePath);
135
- }
136
- }
137
-
138
- const totalFiles = results.reduce((sum, r) => sum + r.total, 0);
139
-
140
- return c.json({ results, totalFiles });
141
- },
142
- );
143
-
144
- export default app;
@@ -1,174 +0,0 @@
1
- import { Hono } from "hono";
2
- import { describeRoute } from "hono-openapi";
3
- import { stat } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import sharp from "sharp";
6
- import { validatePath } from "../lib/path";
7
- import { jsonResponse, requiresAuth } from "../lib/openapi";
8
- import { v } from "../lib/validator";
9
- import { ImageThumbnailQuerySchema, ErrorSchema } from "../schemas";
10
- import { config } from "../config";
11
- import { resolveId } from "../lib/index";
12
-
13
- const app = new Hono();
14
-
15
- // Generate ETag from path, mtime, and thumbnail parameters
16
- const generateETag = (path: string, mtime: Date, params: string): string => {
17
- const hasher = new Bun.CryptoHasher("sha256");
18
- hasher.update(`${path}:${mtime.getTime()}:${params}`);
19
- return `"${hasher.digest("hex").slice(0, 16)}"`;
20
- };
21
-
22
- // Supported image MIME types
23
- const SUPPORTED_IMAGE_TYPES = new Set([
24
- "image/jpeg",
25
- "image/png",
26
- "image/webp",
27
- "image/avif",
28
- "image/tiff",
29
- "image/gif",
30
- "image/svg+xml",
31
- ]);
32
-
33
- // Format to MIME type mapping
34
- const FORMAT_MIME: Record<string, string> = {
35
- webp: "image/webp",
36
- jpeg: "image/jpeg",
37
- png: "image/png",
38
- avif: "image/avif",
39
- };
40
-
41
- const resolveQueryPath = async (
42
- path: string | undefined,
43
- id: string | undefined,
44
- ): Promise<{ ok: true; path: string } | { ok: false; status: 400 | 404; error: string }> => {
45
- if (id) {
46
- if (!config.indexEnabled) {
47
- return { ok: false, status: 400, error: "index disabled" };
48
- }
49
- const resolved = await resolveId(id);
50
- if (!resolved) return { ok: false, status: 404, error: "not found" };
51
- return { ok: true, path: join(resolved.basePath, resolved.relPath) };
52
- }
53
-
54
- if (!path) {
55
- return { ok: false, status: 400, error: "path or id required" };
56
- }
57
-
58
- return { ok: true, path };
59
- };
60
-
61
- // GET /thumbnail/image - Generate image thumbnail
62
- app.get(
63
- "/image",
64
- describeRoute({
65
- tags: ["Thumbnail"],
66
- summary: "Generate image thumbnail",
67
- description:
68
- "Generate a thumbnail from an image file on-the-fly using Sharp. " +
69
- "Supports JPEG, PNG, WebP, AVIF, TIFF, GIF, and SVG input formats.",
70
- ...requiresAuth,
71
- responses: {
72
- 200: {
73
- description: "Thumbnail image",
74
- content: {
75
- "image/webp": { schema: { type: "string", format: "binary" } },
76
- "image/jpeg": { schema: { type: "string", format: "binary" } },
77
- "image/png": { schema: { type: "string", format: "binary" } },
78
- "image/avif": { schema: { type: "string", format: "binary" } },
79
- },
80
- },
81
- 400: jsonResponse(ErrorSchema, "Invalid parameters or not an image"),
82
- 403: jsonResponse(ErrorSchema, "Forbidden"),
83
- 404: jsonResponse(ErrorSchema, "Not found"),
84
- },
85
- }),
86
- v("query", ImageThumbnailQuerySchema),
87
- async (c) => {
88
- const { path, id, width, height, fit, position, format, quality } = c.req.valid("query");
89
-
90
- const resolved = await resolveQueryPath(path, id);
91
- if (!resolved.ok) return c.json({ error: resolved.error }, resolved.status);
92
-
93
- // Validate path
94
- const result = await validatePath(resolved.path);
95
- if (!result.ok) return c.json({ error: result.error }, result.status);
96
-
97
- // Check if file exists and is a file
98
- const file = Bun.file(result.realPath);
99
- if (!(await file.exists())) {
100
- return c.json({ error: "file not found" }, 404);
101
- }
102
-
103
- // Check MIME type
104
- if (!SUPPORTED_IMAGE_TYPES.has(file.type)) {
105
- return c.json(
106
- { error: `unsupported image type: ${file.type}. Supported: ${[...SUPPORTED_IMAGE_TYPES].join(", ")}` },
107
- 400,
108
- );
109
- }
110
-
111
- // Get file stats for Last-Modified and ETag
112
- const fileStat = await stat(result.realPath);
113
- const lastModified = fileStat.mtime;
114
- const paramsKey = `${width}x${height}:${fit}:${position}:${format}:${quality}`;
115
- const etag = generateETag(result.realPath, lastModified, paramsKey);
116
-
117
- // Check If-None-Match (ETag)
118
- const ifNoneMatch = c.req.header("If-None-Match");
119
- if (ifNoneMatch === etag) {
120
- return new Response(null, { status: 304 });
121
- }
122
-
123
- // Check If-Modified-Since
124
- const ifModifiedSince = c.req.header("If-Modified-Since");
125
- if (ifModifiedSince) {
126
- const clientDate = new Date(ifModifiedSince);
127
- if (lastModified <= clientDate) {
128
- return new Response(null, { status: 304 });
129
- }
130
- }
131
-
132
- try {
133
- // Read file and process with Sharp
134
- const buffer = await file.arrayBuffer();
135
-
136
- let pipeline = sharp(Buffer.from(buffer)).resize(width, height, {
137
- fit,
138
- position,
139
- });
140
-
141
- // Apply format and quality
142
- switch (format) {
143
- case "webp":
144
- pipeline = pipeline.webp({ quality });
145
- break;
146
- case "jpeg":
147
- pipeline = pipeline.jpeg({ quality });
148
- break;
149
- case "png":
150
- pipeline = pipeline.png({ quality });
151
- break;
152
- case "avif":
153
- pipeline = pipeline.avif({ quality });
154
- break;
155
- }
156
-
157
- const thumbnail = await pipeline.toBuffer();
158
-
159
- return new Response(thumbnail, {
160
- headers: {
161
- "Content-Type": FORMAT_MIME[format],
162
- "Cache-Control": "public, max-age=31536000, immutable",
163
- "Last-Modified": lastModified.toUTCString(),
164
- ETag: etag,
165
- },
166
- });
167
- } catch (err) {
168
- console.error("[Filegate] Thumbnail error:", err);
169
- return c.json({ error: "failed to generate thumbnail" }, 500);
170
- }
171
- },
172
- );
173
-
174
- export default app;