@thetechfossil/upfiles 0.6.0 → 1.0.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.
package/README.md CHANGED
@@ -1,122 +1,449 @@
1
- # Upfiles Packages Plugin
1
+ # @thetechfossil/upfiles
2
2
 
3
- A comprehensive package solution for managing projects and API keys in Upfiles applications.
3
+ Lightweight JavaScript client and React component to upload files via a presigned S3 flow. Similar to UploadThing-like DX.
4
4
 
5
- ## Overview
5
+ ## Documentation
6
6
 
7
- This package provides both a user-friendly dialog interface and reusable functions for managing projects and API keys. It allows developers to either use the pre-built dialog component or integrate the underlying logic directly into their applications.
7
+ 📚 **Full documentation available at:** [https://ttf-upfiles-docs.netlify.app/](https://ttf-upfiles-docs.netlify.app/)
8
8
 
9
- ## Features
9
+ For detailed API reference, guides, examples, and more information, visit the documentation site.
10
10
 
11
- - Multi-functional dialog for managing projects and API keys
12
- - Reusable functions for programmatic integration
13
- - Full CRUD operations for projects and API keys
14
- - Authentication-aware API interactions
15
- - TypeScript support with comprehensive type definitions
11
+ - Client: get presigned URLs and PUT to S3
12
+ - React `<Uploader />`: drag/drop, progress, single/multiple, accepts/types, size limits
13
+ - React `<ProjectFilesWidget />`: list all files for a project (Plugin API), pick one, and optionally auto-save to your app
16
14
 
17
15
  ## Installation
18
16
 
19
17
  ```bash
18
+ # Bun (recommended)
19
+ bun add @thetechfossil/upfiles
20
+
21
+ # npm
20
22
  npm install @thetechfossil/upfiles
23
+
24
+ # pnpm
25
+ pnpm add @thetechfossil/upfiles
26
+
27
+ # yarn
28
+ yarn add @thetechfossil/upfiles
21
29
  ```
22
30
 
23
- ## API Routes
31
+ ## Quickstart
32
+
33
+ ```tsx
34
+ import { Uploader, ConnectProjectDialog } from '@thetechfossil/upfiles';
35
+ import { useState } from 'react';
36
+
37
+ export default function Page() {
38
+ const [open, setOpen] = useState(false);
39
+ const [apiKey, setApiKey] = useState<string | null>(null);
24
40
 
25
- ### Projects
41
+ return (
42
+ <div className="space-y-6">
43
+ <button className="px-3 py-2 rounded bg-blue-600 text-white" onClick={() => setOpen(true)}>
44
+ Connect to Upfiles
45
+ </button>
46
+
47
+ <Uploader
48
+ clientOptions={{
49
+ baseUrl: process.env.NEXT_PUBLIC_UPFILES_APP_URL!,
50
+ apiKey: apiKey ?? undefined, // set after connecting
51
+ apiKeyHeader: 'authorization',
52
+ }}
53
+ multiple
54
+ dropzoneClassName="border border-dashed rounded-md p-6 cursor-pointer"
55
+ buttonClassName="px-3 py-2 rounded bg-blue-600 text-white"
56
+ />
57
+
58
+ <ConnectProjectDialog
59
+ baseUrl={process.env.NEXT_PUBLIC_UPFILES_APP_URL!}
60
+ open={open}
61
+ onOpenChange={setOpen}
62
+ onConnected={(key) => setApiKey(key)}
63
+ />
64
+ </div>
65
+ );
66
+ }
67
+ ```
26
68
 
27
- - `GET /api/packages/projects` - List all projects for the current user
28
- - `POST /api/packages/projects` - Create a new project for the current user
69
+ ## Server prerequisites
29
70
 
30
- ### API Keys
71
+ Your Upfiles app must expose the Plugin API presign endpoint:
31
72
 
32
- - `GET /api/packages/projects/{projectId}/keys` - List API keys for a project
33
- - `POST /api/packages/projects/{projectId}/keys` - Create a new API key for a project
34
- - `DELETE /api/packages/projects/{projectId}/keys` - Revoke an API key
73
+ - Method: `POST`
74
+ - Path: `/api/plugin/upload/presigned-url` (default; configurable)
75
+ - Body: `{ fileName, fileType, fileSize, folderPath?, projectId? }` (`fileSize` is required)
76
+ - Response: `{ presignedUrl, fileKey, publicUrl, apiKeyId?, projectId? }`
35
77
 
36
- ## Package Components
78
+ This repository already provides a Next.js route at `src/app/api/plugin/upload/presigned-url/route.ts`.
37
79
 
38
- ### 1. Dialog Component
80
+ For plugins/packages calling across origins, authenticate using an API key header as documented below. CORS must allow your plugin origin.
39
81
 
40
- The `ProjectKeyDialog` component provides a complete UI for managing projects and API keys:
82
+ ## Basic usage (React)
41
83
 
42
84
  ```tsx
43
- import ProjectKeyDialog from "@thetechfossil/upfiles/packages/dialog/ProjectKeyDialog";
85
+ import { Uploader } from '@thetechfossil/upfiles';
44
86
 
45
- function MyComponent() {
87
+ export default function Page() {
46
88
  return (
47
- <ProjectKeyDialog
48
- userId="user-123"
49
- onProjectSelected={(projectId) => {
50
- // Handle project selection
89
+ <Uploader
90
+ multiple
91
+ accept={["image/*", "application/pdf"]}
92
+ maxFileSize={100 * 1024 * 1024}
93
+ onComplete={(files) => {
94
+ console.log('uploaded:', files);
51
95
  }}
52
- onApiKeySaved={(apiKey) => {
53
- // Handle API key creation
96
+ onError={(err) => {
97
+ console.error(err);
54
98
  }}
99
+ dropzoneClassName="border border-dashed rounded-md p-6 cursor-pointer"
100
+ buttonClassName="px-3 py-2 rounded bg-blue-600 text-white"
101
+ // Optional: pass clientOptions for cross-origin + API key auth
102
+ // clientOptions={{
103
+ // baseUrl: 'https://YOUR_APP_HOST',
104
+ // // or presignUrl: 'https://YOUR_APP_HOST/api/plugin/upload/presigned-url',
105
+ // apiKey: process.env.NEXT_PUBLIC_UPFILES_API_KEY!,
106
+ // apiKeyHeader: 'authorization', // or 'x-api-key' | 'x-up-api-key'
107
+ // }}
55
108
  />
56
109
  );
57
110
  }
58
111
  ```
59
112
 
60
- ### 2. Reusable Functions
113
+ ## Basic usage (vanilla JS)
61
114
 
62
- #### Project Management
115
+ ```ts
116
+ import { UpfilesClient } from '@thetechfossil/upfiles';
117
+
118
+ // Same-origin by default; only set these when needed:
119
+ const client = new UpfilesClient({
120
+ // presignUrl: 'https://YOUR_APP_HOST/api/plugin/upload/presigned-url', // full override
121
+ // baseUrl: 'https://YOUR_APP_HOST', // used with presignPath
122
+ // presignPath: '/api/plugin/upload/presigned-url', // default
123
+ // withCredentials: true, // for session routes (not typical for Plugin API)
124
+ // Provide API key for Plugin API auth:
125
+ // apiKey: 'upk_...'
126
+ // apiKeyHeader: 'authorization' // or 'x-api-key' | 'x-up-api-key'
127
+ });
128
+
129
+ async function upload(file) {
130
+ const { presignedUrl, publicUrl } = await client.getPresignedUrl({
131
+ fileName: file.name,
132
+ fileType: file.type,
133
+ fileSize: file.size, // required
134
+ folderPath: 'my-plugin/uploads/',
135
+ });
136
+ const res = await client.uploadToS3(presignedUrl, file);
137
+ if (!res.ok) throw new Error('Upload failed');
138
+ return publicUrl;
139
+ }
140
+ ```
141
+
142
+ ## API
143
+
144
+ - `new UpfilesClient(options)`
145
+ - `presignUrl` (optional): full absolute URL to presign endpoint. Highest priority.
146
+ - `baseUrl` (optional): API base origin. Used with `presignPath`.
147
+ - `presignPath` (optional): relative path, default `/api/plugin/upload/presigned-url`.
148
+ - `headers` (optional): extra headers to send.
149
+ - `apiKey` (optional): API key value (e.g., `upk_...`).
150
+ - `apiKeyHeader` (optional): one of `'authorization' | 'x-api-key' | 'x-up-api-key'` (default `'authorization'`). If `'authorization'`, `Bearer` prefix is auto-added for `upk_...`.
151
+ - `withCredentials` (optional): `true` to send cookies.
152
+ - Defaults: same-origin requests to `/api/plugin/upload/presigned-url`.
153
+ - `client.getPresignedUrl({ fileName, fileType, fileSize, projectId?, folderPath? })`
154
+ - `client.uploadToS3(presignedUrl, file)`
155
+ - `client.upload(file, { projectId?, folderPath?, fetchThumbnails? })` → returns `{ publicUrl, fileKey, fileName, fileType, fileSize, projectId?, apiKeyId?, thumbnails? }`
156
+ - `client.getThumbnails(fileKey)` → returns `Thumbnail[]`
157
+ - `client.getProjectFiles({ folderPath? })` → returns `FileListItem[]`
158
+
159
+ - `<Uploader />` props
160
+ - `clientOptions` (optional): same as `UpfilesClient` options (only needed for cross-origin or auth customization)
161
+ - `multiple` (default true)
162
+ - `accept` (array of MIME patterns)
163
+ - `maxFileSize` (bytes, default 100MB)
164
+ - `maxFiles` (default 10)
165
+ - `fetchThumbnails` (boolean): if true, fetch thumbnails after upload via Plugin API
166
+ - `onComplete(files)` and `onError(error)`
167
+ - `className`, `buttonClassName`, `dropzoneClassName`
168
+ - `children`: custom dropzone inner content
169
+
170
+ ### Thumbnails usage
171
+
172
+ Your Upfiles app exposes `GET /api/plugin/thumbnails?fileKey=...` for plugins. Enable `fetchThumbnails` to auto-fetch after upload, or call `client.getThumbnails(fileKey)` yourself.
173
+
174
+ Example with `client.upload`:
63
175
 
64
176
  ```ts
65
- import {
66
- listProjects,
67
- createProject,
68
- updateProject,
69
- deleteProject,
70
- } from "@thetechfossil/upfiles/packages/projectManager";
177
+ const result = await client.upload(file, { folderPath: 'my-plugin/', fetchThumbnails: true });
178
+ console.log(result.thumbnails);
179
+ ```
180
+
181
+ ### List project files (Plugin API)
182
+
183
+ Your Upfiles app exposes `GET /api/plugin/files?folderPath=...` for plugins (API key auth + CORS). The client can call it via `getProjectFiles`:
184
+
185
+ ```ts
186
+ import { UpfilesClient } from '@thetechfossil/upfiles';
187
+
188
+ const client = new UpfilesClient({
189
+ baseUrl: 'https://YOUR_APP_HOST',
190
+ apiKey: 'upk_...',
191
+ thumbnailsPath: '/api/plugin/thumbnails', // used to derive /api/plugin/files
192
+ });
193
+
194
+ const files = await client.getProjectFiles({ folderPath: 'optional/subfolder' });
195
+ // [{ key, originalName, size, contentType, uploadedAt, url }, ...]
196
+ ```
197
+
198
+ ### `<ProjectFilesWidget />`
199
+
200
+ Simple UI to fetch and select a file for the project tied to the API key. Emits the selection, and can optionally POST it to your backend.
201
+
202
+ ```tsx
203
+ import { ProjectFilesWidget } from '@thetechfossil/upfiles';
204
+
205
+ export default function PickFile() {
206
+ return (
207
+ <ProjectFilesWidget
208
+ clientOptions={{
209
+ baseUrl: 'https://YOUR_APP_HOST',
210
+ apiKey: process.env.NEXT_PUBLIC_UPFILES_API_KEY!,
211
+ apiKeyHeader: 'authorization',
212
+ thumbnailsPath: '/api/plugin/thumbnails',
213
+ }}
214
+ // Optional: filter within a folder
215
+ // folderPath="my-plugin/"
216
+
217
+ onSelect={(f) => {
218
+ // { name, key, url, size, contentType }
219
+ console.log('Selected:', f.name, f.key);
220
+ }}
221
+
222
+ // Optional: built-in save
223
+ // saveUrl="/api/files/save" // Your app route to persist selection
224
+ // onSave={async (f) => { await fetch('/api/files/save', { method: 'POST', body: JSON.stringify(f) }); }}
225
+ // onSaved={(result) => console.log('Saved!', result)}
226
+ />
227
+ );
228
+ }
229
+ ```
230
+
231
+ ## Notes
232
+
233
+ - Dev with Vite: configure a proxy so `/api/*` maps to your backend (e.g., `http://localhost:4000`), then `<Uploader />` works with no config.
234
+ - Ensure CORS where applicable (S3 bucket CORS; and set `PLUGIN_ALLOWED_ORIGINS` on your Upfiles app for cross-origin Plugin API calls).
235
+ - For cross-origin with cookies (NextAuth), set `withCredentials: true` and configure your server to allow credentials.
236
+ Plugin API typically uses API keys instead of cookies.
237
+
238
+ ### Plugin endpoints provided by this repo
71
239
 
72
- // List projects
73
- const { projects } = await listProjects("user-123");
240
+ - `POST /api/plugin/upload/presigned-url` – generate presigned S3 URL for upload
241
+ - `GET /api/plugin/thumbnails?fileKey=...` list thumbnails for a file
242
+ - `GET /api/plugin/files?folderPath=...` – list project files (API key scoped), optional folder prefix
74
243
 
75
- // Create project
76
- const newProject = await createProject("user-123", { name: "My Project" });
244
+ ## Connect a project to get an API key
245
+
246
+ This package ships a ready-made dialog built with Radix UI + Tailwind classes to help developers connect their project to your main Upfiles app and obtain an API key.
247
+
248
+ ### Install
249
+
250
+ Your app must have Tailwind configured. The dialog uses Radix primitives (shadcn/ui compatible). Ensure the peer dependency is available in the host app:
251
+
252
+ ```bash
253
+ bun add @thetechfossil/upfiles @radix-ui/react-dialog
254
+ # or
255
+ npm install @thetechfossil/upfiles @radix-ui/react-dialog
77
256
  ```
78
257
 
79
- #### API Key Management
258
+ ### `ConnectProjectDialog` (UI modal)
259
+
260
+ Props:
261
+
262
+ - `baseUrl`: your Upfiles app origin (e.g., `https://upfiles.example.com`).
263
+ - `open`, `onOpenChange`: control the dialog.
264
+ - `onConnected(apiKey, { projectId?, source })`: receives the plaintext API key and metadata.
265
+
266
+ It presents three options:
267
+
268
+ - Connect to existing project → lists projects via `GET /api/projects`, then creates a key via `POST /api/projects/:id/keys`.
269
+ - Add API key manually → text input to paste an existing key.
270
+ - Create new project → `POST /api/projects`, then `POST /api/projects/:id/keys`.
271
+
272
+ ```tsx
273
+ import { useState } from 'react';
274
+ import { ConnectProjectDialog } from '@thetechfossil/upfiles';
275
+
276
+ export default function ConnectKeyButton() {
277
+ const [open, setOpen] = useState(false);
278
+ const [apiKey, setApiKey] = useState<string | null>(null);
279
+
280
+ return (
281
+ <div>
282
+ <button className="px-3 py-2 rounded bg-blue-600 text-white" onClick={() => setOpen(true)}>
283
+ Connect Upfiles
284
+ </button>
285
+
286
+ <ConnectProjectDialog
287
+ baseUrl={process.env.NEXT_PUBLIC_UPFILES_APP_URL!}
288
+ open={open}
289
+ onOpenChange={setOpen}
290
+ onConnected={(key, meta) => {
291
+ setApiKey(key);
292
+ // Persist the key in your own DB as needed (see below)
293
+ console.log('Connected via', meta.source, 'projectId', meta.projectId);
294
+ }}
295
+ />
296
+ </div>
297
+ );
298
+ }
299
+ ```
300
+
301
+ ### Store the key in your own DB
302
+
303
+ You control persistence. Example Next.js route handler to store an encrypted key for the current user:
80
304
 
81
305
  ```ts
82
- import {
83
- listApiKeys,
84
- createApiKey,
85
- revokeApiKey,
86
- } from "@thetechfossil/upfiles/packages/apiKeyManager";
87
-
88
- // List API keys
89
- const { keys } = await listApiKeys("project-456");
90
-
91
- // Create API key
92
- const { key } = await createApiKey({
93
- projectId: "project-456",
94
- name: "CI Pipeline Key",
306
+ // app/api/integrations/upfiles/key/route.ts
307
+ import { NextRequest, NextResponse } from 'next/server';
308
+ import { prisma } from '@/lib/prisma';
309
+ import { getServerSession } from 'next-auth';
310
+ import { authOptions } from '@/lib/auth';
311
+
312
+ export async function POST(req: NextRequest) {
313
+ const session = await getServerSession(authOptions as any);
314
+ const userId = (session as any)?.user?.id as string | undefined;
315
+ if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
316
+
317
+ const { apiKey } = await req.json();
318
+ if (!apiKey) return NextResponse.json({ error: 'apiKey required' }, { status: 400 });
319
+
320
+ // TODO: encrypt before saving
321
+ await prisma.user.update({ where: { id: userId }, data: { upfilesApiKey: apiKey } });
322
+ return NextResponse.json({ ok: true });
323
+ }
324
+ ```
325
+
326
+ From `onConnected`, call this route to persist:
327
+
328
+ ```ts
329
+ await fetch('/api/integrations/upfiles/key', {
330
+ method: 'POST',
331
+ headers: { 'content-type': 'application/json' },
332
+ body: JSON.stringify({ apiKey })
95
333
  });
96
334
  ```
97
335
 
98
- ## Usage Examples
336
+ ### Utility functions (no UI)
337
+
338
+ If you prefer to build a custom UI, use the exported functions:
339
+
340
+ ```ts
341
+ import {
342
+ listProjects,
343
+ createProject,
344
+ connectProject,
345
+ addApiKeyManually,
346
+ createClientWithKey,
347
+ } from '@thetechfossil/upfiles';
348
+
349
+ const baseUrl = 'https://upfiles.example.com';
350
+
351
+ // 1) List existing projects (requires session cookie on baseUrl)
352
+ const projects = await listProjects({ baseUrl });
353
+
354
+ // 2) Create a new project and key
355
+ const { apiKey, projectId } = await createProject({ baseUrl, name: 'My App' });
99
356
 
100
- See `packages/examples/usageExample.ts` for detailed usage examples.
357
+ // 3) Create a key for an existing project
358
+ const key2 = await connectProject({ baseUrl, projectId: 'proj_123' });
101
359
 
102
- ## Comprehensive Package Documentation
360
+ // 4) Manual
361
+ const manual = addApiKeyManually('upk_...');
103
362
 
104
- For detailed package documentation including installation instructions, configuration options, usage examples, available methods and functions, parameter specifications, return value descriptions, troubleshooting guides, best practices, and detailed explanations of all package features and capabilities, see: [`docs/PACKAGE_DOCUMENTATION.md`](docs/PACKAGE_DOCUMENTATION.md)
363
+ // 5) Build an upload client with the key
364
+ const client = createClientWithKey(baseUrl, apiKey);
365
+ ```
366
+
367
+ ### `<ImageManager />` props
368
+
369
+ ```ts
370
+ type ImageManagerProps = {
371
+ open: boolean;
372
+ onOpenChange: (open: boolean) => void;
373
+ clientOptions: UpfilesClientOptions;
374
+ projectId?: string;
375
+ folderPath?: string;
376
+ title?: string;
377
+ description?: string;
378
+ className?: string;
379
+ gridClassName?: string;
380
+ onSelect: (image: {
381
+ url: string;
382
+ key: string;
383
+ originalName: string;
384
+ size: number;
385
+ contentType: string;
386
+ thumbnails?: { id: string; key: string; url: string; size: number; sizeType?: string }[];
387
+ }) => void;
388
+ onDelete?: (key: string) => Promise<void>;
389
+ deleteUrl?: string;
390
+ autoRecordToDb?: boolean;
391
+ fetchThumbnails?: boolean;
392
+ maxFileSize?: number;
393
+ maxFiles?: number;
394
+ mode?: 'full' | 'browse' | 'upload'; // default: 'full'
395
+ showDelete?: boolean; // default: true
396
+ };
397
+ ```
105
398
 
106
- ## Integration
399
+ ### Image picker modal (Browse Mode)
107
400
 
108
- The package can be integrated into existing applications by:
401
+ You can use `ImageManager` in `browse` mode to let users select existing images without the upload tab:
109
402
 
110
- 1. Using the dialog component for user-facing interfaces
111
- 2. Calling the reusable functions for programmatic operations
112
- 3. Implementing custom UI components that use the same underlying logic
403
+ ```tsx
404
+ import { useState } from 'react';
405
+ import { ImageManager } from '@thetechfossil/upfiles';
406
+
407
+ export default function PickImage() {
408
+ const [open, setOpen] = useState(false);
409
+ return (
410
+ <div>
411
+ <button className="px-3 py-2 rounded bg-blue-600 text-white" onClick={() => setOpen(true)}>
412
+ Pick image
413
+ </button>
414
+ <ImageManager
415
+ open={open}
416
+ onOpenChange={setOpen}
417
+ mode="browse"
418
+ clientOptions={{
419
+ baseUrl: process.env.NEXT_PUBLIC_UPFILES_APP_URL!,
420
+ apiKey: process.env.NEXT_PUBLIC_UPFILES_API_KEY!,
421
+ apiKeyHeader: 'authorization',
422
+ }}
423
+ // folderPath="my-plugin/images/" // optional
424
+ onSelect={async (img) => {
425
+ // Persist original image URL in your DB
426
+ await fetch('/api/media/save', {
427
+ method: 'POST',
428
+ headers: { 'content-type': 'application/json' },
429
+ body: JSON.stringify({ url: img.url, key: img.key }),
430
+ });
431
+ }}
432
+ />
433
+ </div>
434
+ );
435
+ }
436
+ ```
113
437
 
114
- ## Security Notes
438
+ ## Reusing across multiple Next.js projects
115
439
 
116
- - API keys are only returned once during creation (plaintext version)
117
- - All operations are authenticated and scoped to the current user
118
- - Revoked keys are marked as inactive but remain in the database
440
+ - Install `@thetechfossil/upfiles` and `@radix-ui/react-dialog` in each project.
441
+ - Set an environment variable like `NEXT_PUBLIC_UPFILES_APP_URL` to point all consumers to your main Upfiles app.
442
+ - Each consumer stores its own copy of the API key in its DB, obtained via `ConnectProjectDialog`.
119
443
 
120
- ## License
444
+ ## Troubleshooting
121
445
 
122
- MIT
446
+ - __401 Unauthorized when listing/creating projects__: The `projects` and `keys` endpoints require a signed-in session on the main app domain. Open the dialog from a page that can send cookies to `{baseUrl}` or run from the same origin; otherwise configure auth/CORS accordingly.
447
+ - __CORS errors calling main app__: Ensure the main app allows your consumer origin in CORS and, if you use cookies, sets the correct `Access-Control-Allow-Credentials` headers and SameSite attributes.
448
+ - __Uploads fail__: Verify S3 bucket CORS and that `POST /api/plugin/upload/presigned-url` is accessible from the consumer app.
449
+ - __API key header__: Default is `Authorization: Bearer upk_...`. If your server expects a different header, set `apiKeyHeader: 'x-api-key' | 'x-up-api-key'` in `UpfilesClient` options.