nextjs-cms 0.9.22 → 0.9.23
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 +65 -13
- package/dist/api/actions/files.d.ts +30 -0
- package/dist/api/actions/files.d.ts.map +1 -0
- package/dist/api/actions/files.js +234 -0
- package/dist/api/actions/index.d.ts +4 -0
- package/dist/api/actions/index.d.ts.map +1 -0
- package/dist/api/actions/index.js +3 -0
- package/dist/api/actions/pages.d.ts +297 -0
- package/dist/api/actions/pages.d.ts.map +1 -0
- package/dist/api/actions/pages.js +1215 -0
- package/dist/api/actions/privileges.d.ts +25 -0
- package/dist/api/actions/privileges.d.ts.map +1 -0
- package/dist/api/actions/privileges.js +98 -0
- package/dist/api/client/index.d.ts +4 -0
- package/dist/api/client/index.d.ts.map +1 -0
- package/dist/api/client/index.js +3 -0
- package/dist/api/client.d.ts +30 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +82 -0
- package/dist/api/index.d.ts +1 -938
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +0 -13
- package/dist/api/plugin/index.d.ts +7 -0
- package/dist/api/plugin/index.d.ts.map +1 -0
- package/dist/api/plugin/index.js +5 -0
- package/dist/api/root.d.ts +18 -1844
- package/dist/api/root.d.ts.map +1 -1
- package/dist/api/root.js +18 -83
- package/dist/api/server/index.d.ts +8 -0
- package/dist/api/server/index.d.ts.map +1 -0
- package/dist/api/server/index.js +3 -0
- package/dist/api/server.d.ts +2748 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +100 -0
- package/dist/api/trpc/client.d.ts +19 -3
- package/dist/api/trpc/client.d.ts.map +1 -1
- package/dist/api/trpc/client.js +55 -1
- package/dist/api/trpc/query-client.d.ts +3 -1
- package/dist/api/trpc/query-client.d.ts.map +1 -1
- package/dist/api/trpc/query-client.js +25 -20
- package/dist/api/trpc/root.d.ts +906 -0
- package/dist/api/trpc/root.d.ts.map +1 -0
- package/dist/api/trpc/root.js +47 -0
- package/dist/api/trpc/routers/accountSettings.d.ts +66 -0
- package/dist/api/trpc/routers/accountSettings.d.ts.map +1 -0
- package/dist/api/trpc/routers/accountSettings.js +200 -0
- package/dist/api/trpc/routers/admins.d.ts +112 -0
- package/dist/api/trpc/routers/admins.d.ts.map +1 -0
- package/dist/api/trpc/routers/admins.js +331 -0
- package/dist/api/trpc/routers/auth.d.ts +54 -0
- package/dist/api/trpc/routers/auth.d.ts.map +1 -0
- package/dist/api/trpc/routers/auth.js +50 -0
- package/dist/api/trpc/routers/categorySection.d.ts +105 -0
- package/dist/api/trpc/routers/categorySection.d.ts.map +1 -0
- package/dist/api/trpc/routers/categorySection.js +49 -0
- package/dist/api/trpc/routers/config.d.ts +48 -0
- package/dist/api/trpc/routers/config.d.ts.map +1 -0
- package/dist/api/trpc/routers/config.js +18 -0
- package/dist/api/trpc/routers/cpanel.d.ts +82 -0
- package/dist/api/trpc/routers/cpanel.d.ts.map +1 -0
- package/dist/api/trpc/routers/cpanel.js +216 -0
- package/dist/api/trpc/routers/fields.d.ts +35 -0
- package/dist/api/trpc/routers/fields.d.ts.map +1 -0
- package/dist/api/trpc/routers/fields.js +81 -0
- package/dist/api/trpc/routers/files.d.ts +34 -0
- package/dist/api/trpc/routers/files.d.ts.map +1 -0
- package/dist/api/trpc/routers/files.js +14 -0
- package/dist/api/trpc/routers/gallery.d.ts +35 -0
- package/dist/api/trpc/routers/gallery.d.ts.map +1 -0
- package/dist/api/trpc/routers/gallery.js +92 -0
- package/dist/api/trpc/routers/hasItemsSection.d.ts +194 -0
- package/dist/api/trpc/routers/hasItemsSection.d.ts.map +1 -0
- package/dist/api/trpc/routers/hasItemsSection.js +86 -0
- package/dist/api/trpc/routers/logs.d.ts +59 -0
- package/dist/api/trpc/routers/logs.d.ts.map +1 -0
- package/dist/api/trpc/routers/logs.js +79 -0
- package/dist/api/trpc/routers/navigation.d.ts +65 -0
- package/dist/api/trpc/routers/navigation.d.ts.map +1 -0
- package/dist/api/trpc/routers/navigation.js +11 -0
- package/dist/api/trpc/routers/simpleSection.d.ts +93 -0
- package/dist/api/trpc/routers/simpleSection.d.ts.map +1 -0
- package/dist/api/trpc/routers/simpleSection.js +54 -0
- package/dist/api/trpc/server.d.ts +2789 -5
- package/dist/api/trpc/server.d.ts.map +1 -1
- package/dist/api/trpc/server.js +91 -52
- package/dist/api/trpc/trpc.d.ts +111 -0
- package/dist/api/trpc/trpc.d.ts.map +1 -0
- package/dist/api/trpc/trpc.js +99 -0
- package/dist/api/trpc/utils/async-caller-proxy.d.ts +2 -0
- package/dist/api/trpc/utils/async-caller-proxy.d.ts.map +1 -0
- package/dist/api/trpc/utils/async-caller-proxy.js +38 -0
- package/dist/api/trpc/utils/refresh-token-link.d.ts +6 -0
- package/dist/api/trpc/utils/refresh-token-link.d.ts.map +1 -0
- package/dist/api/trpc/utils/refresh-token-link.js +81 -0
- package/dist/api/trpc/utils/router-types.d.ts +7 -0
- package/dist/api/trpc/utils/router-types.d.ts.map +1 -0
- package/dist/api/trpc/utils/router-types.js +0 -0
- package/dist/api/use-axios-private.d.ts +6 -0
- package/dist/api/use-axios-private.d.ts.map +1 -0
- package/dist/api/use-axios-private.js +57 -0
- package/dist/api/utils/async-caller-proxy.d.ts +2 -0
- package/dist/api/utils/async-caller-proxy.d.ts.map +1 -0
- package/dist/api/utils/async-caller-proxy.js +36 -0
- package/dist/api/utils/lazy-caller-proxy.d.ts +2 -0
- package/dist/api/utils/lazy-caller-proxy.d.ts.map +1 -0
- package/dist/api/utils/lazy-caller-proxy.js +36 -0
- package/dist/api/utils/router-types.d.ts +7 -0
- package/dist/api/utils/router-types.d.ts.map +1 -0
- package/dist/api/utils/router-types.js +0 -0
- package/dist/auth/hooks/index.d.ts +1 -2
- package/dist/auth/hooks/index.d.ts.map +1 -1
- package/dist/auth/hooks/index.js +1 -2
- package/dist/auth/react.d.ts +1 -2
- package/dist/auth/react.d.ts.map +1 -1
- package/dist/auth/react.js +1 -2
- package/dist/auth/trpc.d.ts +1 -1
- package/dist/auth/trpc.d.ts.map +1 -1
- package/dist/auth/trpc.js +0 -1
- package/dist/cli/lib/fix-master-admin.d.ts.map +1 -1
- package/dist/cli/lib/fix-master-admin.js +12 -25
- package/dist/cli/lib/update-sections.d.ts.map +1 -1
- package/dist/cli/lib/update-sections.js +29 -24
- package/dist/core/config/config-loader.d.ts +23 -7
- package/dist/core/config/config-loader.d.ts.map +1 -1
- package/dist/core/config/config-loader.js +26 -9
- package/dist/core/fields/select.d.ts +1 -1
- package/dist/core/sections/category.d.ts +44 -44
- package/dist/core/sections/hasItems.d.ts +44 -44
- package/dist/core/sections/section.d.ts +23 -23
- package/dist/core/sections/simple.d.ts +8 -8
- package/dist/core/types/index.d.ts +17 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/plugins/client.d.ts +19 -0
- package/dist/plugins/client.d.ts.map +1 -0
- package/dist/plugins/client.js +24 -0
- package/dist/plugins/define.d.ts +4 -0
- package/dist/plugins/define.d.ts.map +1 -0
- package/dist/plugins/define.js +3 -0
- package/dist/plugins/derive.d.ts +32 -0
- package/dist/plugins/derive.d.ts.map +1 -0
- package/dist/plugins/derive.js +77 -0
- package/dist/plugins/loader.d.ts +51 -7
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +111 -51
- package/dist/plugins/manifest.d.ts +28 -0
- package/dist/plugins/manifest.d.ts.map +1 -0
- package/dist/plugins/manifest.js +83 -0
- package/dist/plugins/prefetch.d.ts +16 -0
- package/dist/plugins/prefetch.d.ts.map +1 -0
- package/dist/plugins/prefetch.js +40 -0
- package/dist/plugins/registry.d.ts +22 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +25 -0
- package/dist/plugins/server.d.ts +2 -0
- package/dist/plugins/server.d.ts.map +1 -1
- package/dist/plugins/server.js +2 -0
- package/dist/plugins/types.d.ts +9 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +0 -0
- package/dist/translations/base/en.d.ts +1 -0
- package/dist/translations/base/en.d.ts.map +1 -1
- package/dist/translations/base/en.js +1 -0
- package/dist/translations/client.d.ts +16 -4
- package/dist/translations/client.d.ts.map +1 -1
- package/dist/translations/server.d.ts +16 -4
- package/dist/translations/server.d.ts.map +1 -1
- package/dist/utils/console-log.d.ts +18 -0
- package/dist/utils/console-log.d.ts.map +1 -0
- package/dist/utils/console-log.js +28 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/log.d.ts +18 -0
- package/dist/utils/log.d.ts.map +1 -0
- package/dist/utils/log.js +28 -0
- package/dist/validators/index.d.ts +1 -0
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +1 -0
- package/dist/validators/tags.d.ts +4 -0
- package/dist/validators/tags.d.ts.map +1 -0
- package/dist/validators/tags.js +8 -0
- package/package.json +34 -16
package/README.md
CHANGED
|
@@ -175,18 +175,70 @@ export default categorySection({
|
|
|
175
175
|
})
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
## API Reference
|
|
179
|
-
|
|
180
|
-
###
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
## API Reference
|
|
179
|
+
|
|
180
|
+
### Extending tRPC
|
|
181
|
+
|
|
182
|
+
Create an app-owned tRPC server entrypoint so custom routers are included in the final `AppRouter` type:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// app/_trpc/server.ts
|
|
186
|
+
import 'server-only'
|
|
187
|
+
|
|
188
|
+
import { createTRPCRouter } from 'nextjs-cms/api/server'
|
|
189
|
+
import { dashboardRouter } from './routers/dashboard'
|
|
190
|
+
|
|
191
|
+
export const trpc = createTRPCRouter({
|
|
192
|
+
routers: {
|
|
193
|
+
dashboard: dashboardRouter,
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
export type AppRouter = typeof trpc.appRouter
|
|
198
|
+
export const { api, HydrateClient } = trpc
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Create the matching client entrypoint:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// app/_trpc/client.tsx
|
|
205
|
+
'use client'
|
|
206
|
+
|
|
207
|
+
import { createNextjsCmsClient } from 'nextjs-cms/api/client'
|
|
208
|
+
import type { AppRouter } from './server'
|
|
209
|
+
|
|
210
|
+
export const { trpc, NextjsCmsProvider } = createNextjsCmsClient<AppRouter>()
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Export final app router inference helpers from the app:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// app/_trpc/types.ts
|
|
217
|
+
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server'
|
|
218
|
+
import type { AppRouter } from './server'
|
|
219
|
+
|
|
220
|
+
export type RouterInputs = inferRouterInputs<AppRouter>
|
|
221
|
+
export type RouterOutputs = inferRouterOutputs<AppRouter>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Mount the handler from the app-owned router:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// app/api/trpc/[trpc]/route.ts
|
|
228
|
+
import { trpc } from '@/app/_trpc/server'
|
|
229
|
+
|
|
230
|
+
export const { GET, POST } = trpc.handler
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Core Modules
|
|
234
|
+
|
|
235
|
+
#### API
|
|
236
|
+
- **Server runtime**: `createTRPCRouter`, procedures, and `createTRPCContext` from `nextjs-cms/api/server`
|
|
237
|
+
- **Client runtime**: `createNextjsCmsClient` and `useAxiosPrivate` from `nextjs-cms/api/client`
|
|
238
|
+
|
|
239
|
+
#### Authentication (`nextjs-cms/auth`)
|
|
240
|
+
- **JWT Management**: Token creation, verification, and refresh
|
|
241
|
+
- **React Hooks**: `useSession`, `useRefreshToken`
|
|
190
242
|
- **Server Actions**: Authentication utilities for server components
|
|
191
243
|
|
|
192
244
|
#### Core (`nextjs-cms/core`)
|
|
@@ -288,4 +340,4 @@ Contributions are welcome! Please read our contributing guidelines and submit pu
|
|
|
288
340
|
|
|
289
341
|
## License
|
|
290
342
|
|
|
291
|
-
MIT License - see LICENSE file for details.
|
|
343
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import type { Session } from '../../auth/index.js';
|
|
3
|
+
export declare const getDocument: (session: Session, input: {
|
|
4
|
+
name: string;
|
|
5
|
+
sectionName: string;
|
|
6
|
+
fieldName: string;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
base64: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const getPhoto: (input: {
|
|
12
|
+
name: string;
|
|
13
|
+
folder: string;
|
|
14
|
+
isThumb?: boolean;
|
|
15
|
+
}) => Promise<string>;
|
|
16
|
+
export declare function streamPhoto(input: {
|
|
17
|
+
name: string;
|
|
18
|
+
folder: string;
|
|
19
|
+
isThumb?: boolean;
|
|
20
|
+
}): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
|
|
21
|
+
export declare const getVideo: (session: Session, input: {
|
|
22
|
+
name: string;
|
|
23
|
+
sectionName: string;
|
|
24
|
+
fieldName: string;
|
|
25
|
+
}) => Promise<never>;
|
|
26
|
+
export declare const streamFile: (path: string, options?: {
|
|
27
|
+
start?: number;
|
|
28
|
+
end?: number;
|
|
29
|
+
}) => Promise<ReadableStream<Uint8Array>>;
|
|
30
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/api/actions/files.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAWpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAOlD,eAAO,MAAM,WAAW,GACpB,SAAS,OAAO,EAChB,OAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;;;EA4GlE,CAAA;AA2DD,eAAO,MAAM,QAAQ,GAAU,OAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,oBAgDxF,CAAA;AAGD,wBAAsB,WAAW,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,wDAyB3F;AAGD,eAAO,MAAM,QAAQ,GAAU,SAAS,OAAO,EAAE,OAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,mBAE/G,CAAA;AAGD,eAAO,MAAM,UAAU,GACnB,MAAM,MAAM,EACZ,UAAU;IACN,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;CACf,KACF,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAGpC,CAAA"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
import { TRPCError } from '@trpc/server';
|
|
6
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
7
|
+
import { readChunk } from 'read-chunk';
|
|
8
|
+
import sharp from 'sharp';
|
|
9
|
+
import through2 from 'through2';
|
|
10
|
+
import { getCMSConfig } from '../../core/config/index.js';
|
|
11
|
+
import { SectionFactory } from '../../core/factories/index.js';
|
|
12
|
+
import getString from '../../translations/index.js';
|
|
13
|
+
import { sanitizeFileName, sanitizeFolderOrFileName } from '../../utils/index.js';
|
|
14
|
+
export const getDocument = async (session, input) => {
|
|
15
|
+
const { name, sectionName, fieldName } = input;
|
|
16
|
+
// Sanitize the inputs
|
|
17
|
+
const sanitizedFolder = sanitizeFolderOrFileName(sectionName);
|
|
18
|
+
const sanitizedName = sanitizeFileName(name);
|
|
19
|
+
/**
|
|
20
|
+
* Check the section and the field name, and get the allowed extensions,
|
|
21
|
+
* while also checking if the user has access to the section
|
|
22
|
+
*/
|
|
23
|
+
const section = await SectionFactory.getSectionForAdmin({
|
|
24
|
+
name: sanitizedFolder,
|
|
25
|
+
admin: { id: session.user.id },
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* If the check fails, throw an error
|
|
29
|
+
*/
|
|
30
|
+
if (!section?.name) {
|
|
31
|
+
throw new TRPCError({
|
|
32
|
+
code: 'BAD_REQUEST',
|
|
33
|
+
message: getString('invalidFilePath', session.user.language),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const fieldConfig = section.fields.find((field) => field.name === fieldName);
|
|
37
|
+
if (!fieldConfig || typeof fieldConfig.build !== 'function') {
|
|
38
|
+
throw new TRPCError({
|
|
39
|
+
code: 'BAD_REQUEST',
|
|
40
|
+
message: getString('invalidRequest', session.user.language),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const field = fieldConfig.build();
|
|
44
|
+
/**
|
|
45
|
+
* If field is not found, throw an error
|
|
46
|
+
*/
|
|
47
|
+
if (!field || !field.name || !field.extensions || field.extensions.length === 0) {
|
|
48
|
+
throw new TRPCError({
|
|
49
|
+
code: 'BAD_REQUEST',
|
|
50
|
+
message: getString('invalidRequest', session.user.language),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Split the allowed extensions into an array
|
|
55
|
+
*/
|
|
56
|
+
const uploadsFolder = (await getCMSConfig()).media.upload.path;
|
|
57
|
+
const documentAllowedExtensions = field.extensions;
|
|
58
|
+
const dir = '.documents';
|
|
59
|
+
const pathToFile = path.join(uploadsFolder, dir, sanitizedFolder, sanitizedName);
|
|
60
|
+
/**
|
|
61
|
+
* First, check if the file exists
|
|
62
|
+
*/
|
|
63
|
+
if (!fs.existsSync(pathToFile)) {
|
|
64
|
+
throw new TRPCError({
|
|
65
|
+
code: 'BAD_REQUEST',
|
|
66
|
+
message: getString('fileNotFound', session.user.language),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Read the first 4100 bytes of the file
|
|
71
|
+
*/
|
|
72
|
+
const chunkBuffer = await readChunk(pathToFile, { length: 4100 });
|
|
73
|
+
/**
|
|
74
|
+
* Get the file type from the buffer
|
|
75
|
+
*/
|
|
76
|
+
const fileType = await fileTypeFromBuffer(chunkBuffer);
|
|
77
|
+
/**
|
|
78
|
+
* If the file type is invalid, return an error
|
|
79
|
+
*/
|
|
80
|
+
if (!fileType) {
|
|
81
|
+
throw new TRPCError({
|
|
82
|
+
code: 'BAD_REQUEST',
|
|
83
|
+
message: getString('invalidFileType', session.user.language),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if the file type is allowed
|
|
88
|
+
*/
|
|
89
|
+
if (!documentAllowedExtensions.includes(fileType.ext)) {
|
|
90
|
+
throw new TRPCError({
|
|
91
|
+
code: 'BAD_REQUEST',
|
|
92
|
+
message: getString('invalidFileType', session.user.language),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Convert the image to webp format and return a buffer
|
|
97
|
+
*/
|
|
98
|
+
const buffer = fs.readFileSync(pathToFile);
|
|
99
|
+
/**
|
|
100
|
+
* Create a base64 string from the buffer
|
|
101
|
+
*/
|
|
102
|
+
const base64 = buffer.toString('base64');
|
|
103
|
+
/**
|
|
104
|
+
* Return the base64 string with the mime type
|
|
105
|
+
*/
|
|
106
|
+
return {
|
|
107
|
+
base64: `data:${fileType.mime};base64,${base64}`,
|
|
108
|
+
mimeType: fileType.mime,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Helper function with proper cleanup for converting Node.js stream to Web ReadableStream
|
|
113
|
+
* Uses pull()-based reading for proper backpressure support.
|
|
114
|
+
* @param nodeStream
|
|
115
|
+
* @param sharpInstance
|
|
116
|
+
*/
|
|
117
|
+
function nodeStreamToWebStream(nodeStream, sharpInstance) {
|
|
118
|
+
return new ReadableStream({
|
|
119
|
+
start() {
|
|
120
|
+
// Start paused — let pull() drive reading
|
|
121
|
+
if ('pause' in nodeStream && typeof nodeStream.pause === 'function') {
|
|
122
|
+
nodeStream.pause();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
pull(controller) {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const onData = (chunk) => {
|
|
128
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
129
|
+
if ('pause' in nodeStream && typeof nodeStream.pause === 'function') {
|
|
130
|
+
nodeStream.pause();
|
|
131
|
+
}
|
|
132
|
+
nodeStream.removeListener('data', onData);
|
|
133
|
+
nodeStream.removeListener('error', onError);
|
|
134
|
+
resolve();
|
|
135
|
+
};
|
|
136
|
+
const onError = (err) => {
|
|
137
|
+
controller.error(err);
|
|
138
|
+
nodeStream.removeListener('data', onData);
|
|
139
|
+
reject(err);
|
|
140
|
+
};
|
|
141
|
+
nodeStream.on('data', onData);
|
|
142
|
+
nodeStream.on('error', onError);
|
|
143
|
+
nodeStream.once('end', () => {
|
|
144
|
+
controller.close();
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
if ('resume' in nodeStream && typeof nodeStream.resume === 'function') {
|
|
148
|
+
nodeStream.resume();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
cancel() {
|
|
153
|
+
// Proper cleanup sequence
|
|
154
|
+
sharpInstance.destroy();
|
|
155
|
+
nodeStream.removeAllListeners();
|
|
156
|
+
if ('destroy' in nodeStream && typeof nodeStream.destroy === 'function') {
|
|
157
|
+
nodeStream.destroy();
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
export const getPhoto = async (input) => {
|
|
163
|
+
const { name, folder, isThumb } = input;
|
|
164
|
+
// Sanitize the inputs
|
|
165
|
+
const sanitizedFolder = sanitizeFolderOrFileName(folder);
|
|
166
|
+
const sanitizedName = sanitizeFileName(name);
|
|
167
|
+
/**
|
|
168
|
+
* Check the folder name matches a section name in the database
|
|
169
|
+
* Notice: Maybe we don't need this check because making an sql call for each image is maybe expensive
|
|
170
|
+
*/
|
|
171
|
+
/*const sectionQuery = await db
|
|
172
|
+
.select()
|
|
173
|
+
.from(SectionsTable)
|
|
174
|
+
.where(eq(SectionsTable.sectionName, sanitizedFolder))
|
|
175
|
+
.limit(1)
|
|
176
|
+
|
|
177
|
+
if (sectionQuery.length === 0) {
|
|
178
|
+
throw new Error('Invalid folder name')
|
|
179
|
+
}*/
|
|
180
|
+
const uploadsFolder = (await getCMSConfig()).media.upload.path;
|
|
181
|
+
const dir = isThumb ? '.thumbs' : '.photos';
|
|
182
|
+
const pathToFile = path.join(uploadsFolder, dir, sanitizedFolder, sanitizedName);
|
|
183
|
+
/**
|
|
184
|
+
* Disable caching for the image to avoid unlink issues when removing the image
|
|
185
|
+
*/
|
|
186
|
+
sharp.cache({ files: 0 });
|
|
187
|
+
sharp.cache(false);
|
|
188
|
+
/**
|
|
189
|
+
* Convert the image to webp format and return a buffer
|
|
190
|
+
*/
|
|
191
|
+
const sharpInstance = sharp(pathToFile);
|
|
192
|
+
let webpBuffer = await sharpInstance // Load the image
|
|
193
|
+
.toFormat('webp') // Re-encode the image to webp
|
|
194
|
+
.withExif({}) // Strip the exif data
|
|
195
|
+
.toBuffer(); // Return a buffer
|
|
196
|
+
/**
|
|
197
|
+
* Destroy the sharp instance to free it from memory
|
|
198
|
+
*/
|
|
199
|
+
const base64String = webpBuffer.toString('base64');
|
|
200
|
+
sharpInstance.destroy();
|
|
201
|
+
/**
|
|
202
|
+
* Return the base64 string with the mime type
|
|
203
|
+
*/
|
|
204
|
+
return `data:image/webp;base64,${base64String}`;
|
|
205
|
+
};
|
|
206
|
+
export async function streamPhoto(input) {
|
|
207
|
+
const { name, folder, isThumb } = input;
|
|
208
|
+
// Sanitize the inputs
|
|
209
|
+
const sanitizedFolder = sanitizeFolderOrFileName(folder);
|
|
210
|
+
const sanitizedName = sanitizeFileName(name);
|
|
211
|
+
const dir = isThumb ? '.thumbs' : '.photos';
|
|
212
|
+
const uploadsFolder = (await getCMSConfig()).media.upload.path;
|
|
213
|
+
const pathToFile = path.join(uploadsFolder, dir, sanitizedFolder, sanitizedName);
|
|
214
|
+
/**
|
|
215
|
+
* Disable caching for the image to avoid unlink issues when removing the image
|
|
216
|
+
*/
|
|
217
|
+
sharp.cache({ files: 0 });
|
|
218
|
+
sharp.cache(false);
|
|
219
|
+
// Process all images through Sharp
|
|
220
|
+
const processedImage = sharp(pathToFile).toFormat('webp').withExif({});
|
|
221
|
+
/**
|
|
222
|
+
* Convert Node.js stream to Web ReadableStream
|
|
223
|
+
* Also, add through2 for proper streaming
|
|
224
|
+
*/
|
|
225
|
+
const nodeStream = processedImage.pipe(through2());
|
|
226
|
+
return nodeStreamToWebStream(nodeStream, processedImage);
|
|
227
|
+
}
|
|
228
|
+
export const getVideo = async (session, input) => {
|
|
229
|
+
throw new Error(getString('useVideoApiRoute', session.user.language));
|
|
230
|
+
};
|
|
231
|
+
export const streamFile = async (path, options) => {
|
|
232
|
+
const stream = fs.createReadStream(path, options);
|
|
233
|
+
return Readable.toWeb(stream);
|
|
234
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA"}
|