nuxt-file-storage 0.2.9 → 0.3.1

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
@@ -8,7 +8,7 @@
8
8
  [![License][license-src]][license-href]
9
9
  [![Nuxt][nuxt-src]][nuxt-href]
10
10
 
11
- Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.
11
+ Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and receive them from the backend to then save the files in your project.
12
12
 
13
13
  - [✨  Release Notes](/CHANGELOG.md)
14
14
  - [🏀 Online playground](https://stackblitz.com/github/NyllRE/nuxt-file-storage?file=playground%2Fapp.vue)
@@ -128,13 +128,15 @@ You have to create a new instance of `useFileStorage` for each input field
128
128
  } = useFileStorage() ← | 2 |
129
129
  </script>
130
130
  ```
131
- by calling a new `useFileStorage` instance you seperate the internal logic between the inputs
131
+ by calling a new `useFileStorage` instance you separate the internal logic between the inputs
132
132
 
133
133
  ### Handling files in the backend
134
- using Nitro Server Engine, we will make an api route that recieves the files and stores them in the folder `userFiles`
134
+ using Nitro Server Engine, we will make an api route that receives the files and stores them in the folder `userFiles`
135
135
  ```ts
136
+ import { ServerFile } from "nuxt-file-storage";
137
+
136
138
  export default defineEventHandler(async (event) => {
137
- const { files } = await readBody<{ files: File[] }>(event)
139
+ const { files } = await readBody<{ files: ServerFile[] }>(event)
138
140
 
139
141
  for ( const file of files ) {
140
142
  await storeFileLocally(
@@ -149,13 +151,20 @@ export default defineEventHandler(async (event) => {
149
151
  const { binaryString, ext } = parseDataUrl(file.content)
150
152
  }
151
153
 
152
- return 'success!'
153
- })
154
+ // Deleting Files
155
+ await deleteFile('requiredFile.txt', '/userFiles')
154
156
 
155
- interface File {
156
- name: string
157
- content: string
158
- }
157
+ // Get file path
158
+ return await getFileLocally('requiredFile.txt', '/userFiles')
159
+ // returns: {AbsolutePath}/userFiles/requiredFile.txt
160
+
161
+ // Return a NodeStream of the file
162
+ // uses getFileLocally internally
163
+ return await retrieveFileLocally(event, 'requiredFile.txt', '/userFiles')
164
+
165
+ // Get all files in a folder
166
+ return await getFilesLocally('/userFiles')
167
+ })
159
168
  ```
160
169
 
161
170
  And that's it! Now you can store any file in your nuxt project from the user ✨
package/dist/module.d.mts CHANGED
@@ -19,6 +19,14 @@ interface ModuleOptions {
19
19
  version: string
20
20
  }
21
21
 
22
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
22
+ /**
23
+ * @description Augment the '#imports' module to include useRuntimeConfig
24
+ * this is only needed because this package is consumed as a module
25
+ */
26
+ declare module '#imports' {
27
+ export function useRuntimeConfig(): any
28
+ }
29
+
30
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
23
31
 
24
32
  export { type ClientFile, type ModuleOptions, type ServerFile, _default as default };
package/dist/module.d.ts CHANGED
@@ -19,6 +19,14 @@ interface ModuleOptions {
19
19
  version: string
20
20
  }
21
21
 
22
- declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
22
+ /**
23
+ * @description Augment the '#imports' module to include useRuntimeConfig
24
+ * this is only needed because this package is consumed as a module
25
+ */
26
+ declare module '#imports' {
27
+ export function useRuntimeConfig(): any
28
+ }
29
+
30
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
23
31
 
24
32
  export { type ClientFile, type ModuleOptions, type ServerFile, _default as default };
package/dist/module.json CHANGED
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-file-storage",
3
3
  "configKey": "fileStorage",
4
- "version": "0.2.9"
4
+ "version": "0.3.1",
5
+ "builder": {
6
+ "@nuxt/module-builder": "0.8.4",
7
+ "unbuild": "2.0.0"
8
+ }
5
9
  }
package/dist/module.mjs CHANGED
@@ -1,8 +1,15 @@
1
1
  import { defineNuxtModule, logger, createResolver, addImportsDir, addServerScanDir } from '@nuxt/kit';
2
- import defu from 'defu';
2
+ import { defu } from 'defu';
3
3
 
4
- const version = "0.2.9";
5
4
 
5
+
6
+ // -- Unbuild CommonJS Shims --
7
+ import __cjs_url__ from 'url';
8
+ import __cjs_path__ from 'path';
9
+ import __cjs_mod__ from 'module';
10
+ const __filename = __cjs_url__.fileURLToPath(import.meta.url);
11
+ const __dirname = __cjs_path__.dirname(__filename);
12
+ const require = __cjs_mod__.createRequire(import.meta.url);
6
13
  const module = defineNuxtModule({
7
14
  meta: {
8
15
  name: "nuxt-file-storage",
@@ -18,19 +25,8 @@ const module = defineNuxtModule({
18
25
  config.public.fileStorage = defu(config.public.fileStorage, {
19
26
  ...options
20
27
  });
21
- if (nuxt.options.dev) {
22
- //! I couldn't find a way to detect new updates to warn one time so it will warn every time the server runs again, if you know how to warn about a breaking change only the first run after an update feel free to open a new pull request
23
- const previousVersion = config.public.fileStorage?.version;
24
- if (!previousVersion || previousVersion !== version) {
25
- logger.warn(
26
- `There is a breaking change in the \`storeFileLocally\` method, link to changelog:
27
- https://github.com/NyllRE/nuxt-file-storage/releases/tag/v${version}
28
- `
29
- );
30
- config.public.fileStorage.version = version;
31
- }
32
- }
33
- const resolve = createResolver(import.meta.url).resolve;
28
+ logger.ready(`Nuxt File Storage has mounted successfully`);
29
+ const resolve = createResolver(__dirname).resolve;
34
30
  addImportsDir(resolve("runtime/composables"));
35
31
  addServerScanDir(resolve("./runtime/server"));
36
32
  }
@@ -1,3 +1,4 @@
1
+ import type { ClientFile } from '../../types.js';
1
2
  type Options = {
2
3
  clearOldFiles: boolean;
3
4
  };
@@ -5,18 +6,35 @@ export default function (options?: Options): {
5
6
  files: import("vue").Ref<{
6
7
  content: string | {
7
8
  readonly byteLength: number;
8
- slice: (begin: number, end?: number | undefined) => ArrayBuffer;
9
- readonly [Symbol.toStringTag]: string;
9
+ slice: (begin?: number, end?: number) => ArrayBuffer;
10
+ readonly [Symbol.toStringTag]: "ArrayBuffer";
10
11
  } | null | undefined;
11
12
  name: string;
12
13
  lastModified: number;
13
14
  readonly size: number;
14
15
  readonly type: string;
15
16
  arrayBuffer: () => Promise<ArrayBuffer>;
16
- slice: (start?: number | undefined, end?: number | undefined, contentType?: string | undefined) => Blob;
17
- stream: () => ReadableStream<Uint8Array>;
17
+ bytes: () => Promise<Uint8Array<ArrayBuffer>>;
18
+ slice: (start?: number, end?: number, contentType?: string) => Blob;
19
+ stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
20
+ text: () => Promise<string>;
21
+ }[], ClientFile[] | {
22
+ content: string | {
23
+ readonly byteLength: number;
24
+ slice: (begin?: number, end?: number) => ArrayBuffer;
25
+ readonly [Symbol.toStringTag]: "ArrayBuffer";
26
+ } | null | undefined;
27
+ name: string;
28
+ lastModified: number;
29
+ readonly size: number;
30
+ readonly type: string;
31
+ arrayBuffer: () => Promise<ArrayBuffer>;
32
+ bytes: () => Promise<Uint8Array<ArrayBuffer>>;
33
+ slice: (start?: number, end?: number, contentType?: string) => Blob;
34
+ stream: () => ReadableStream<Uint8Array<ArrayBuffer>>;
18
35
  text: () => Promise<string>;
19
36
  }[]>;
20
37
  handleFileInput: (event: any) => Promise<void>;
38
+ clearFiles: () => void;
21
39
  };
22
40
  export {};
@@ -21,9 +21,12 @@ export default function(options = { clearOldFiles: true }) {
21
21
  reader.readAsDataURL(file);
22
22
  });
23
23
  };
24
+ const clearFiles = () => {
25
+ files.value.splice(0, files.value.length);
26
+ };
24
27
  const handleFileInput = async (event) => {
25
28
  if (options.clearOldFiles) {
26
- files.value.splice(0, files.value.length);
29
+ clearFiles();
27
30
  }
28
31
  const promises = [];
29
32
  for (const file of event.target.files) {
@@ -33,6 +36,7 @@ export default function(options = { clearOldFiles: true }) {
33
36
  };
34
37
  return {
35
38
  files,
36
- handleFileInput
39
+ handleFileInput,
40
+ clearFiles
37
41
  };
38
42
  }
@@ -0,0 +1,9 @@
1
+ export declare const normalizeRelative: (p: string) => string;
2
+ export declare const isSafeBasename: (name: string) => boolean;
3
+ export declare const ensureSafeBasename: (name: string) => string;
4
+ export declare const containsPathTraversal: (p: string) => boolean;
5
+ /**
6
+ * Resolve a target path relative to a mount and ensure it cannot escape the mount.
7
+ * Throws on any suspicious input (path traversal, symlink escape, absolute outside mount).
8
+ */
9
+ export declare const resolveAndEnsureInside: (mount: string, ...parts: string[]) => Promise<string>;
@@ -0,0 +1,69 @@
1
+ import path from "path";
2
+ import { realpath, stat } from "fs/promises";
3
+ import { createError } from "#imports";
4
+ export const normalizeRelative = (p) => {
5
+ if (!p)
6
+ return "";
7
+ return p.replace(/^[/\\]+/, "").replace(/\\/g, "/");
8
+ };
9
+ export const isSafeBasename = (name) => {
10
+ if (!name)
11
+ return false;
12
+ if (name !== path.basename(name))
13
+ return false;
14
+ if (name.includes("\0"))
15
+ return false;
16
+ if (name === "." || name === "..")
17
+ return false;
18
+ if (name.includes("/") || name.includes("\\"))
19
+ return false;
20
+ if (name.split(/[/\\]+/).includes(".."))
21
+ return false;
22
+ return true;
23
+ };
24
+ export const ensureSafeBasename = (name) => {
25
+ if (!isSafeBasename(name))
26
+ throw new Error("Unsafe filename");
27
+ return name;
28
+ };
29
+ export const containsPathTraversal = (p) => {
30
+ if (!p)
31
+ return false;
32
+ if (/^([\\/]|^)?\.\.([\\/]|$)?/.test(p) || /(^|[\\/])\.\.($|[\\/])/.test(p))
33
+ return true;
34
+ const normalized = path.normalize(p);
35
+ const parts = normalized.split(/[/\\]+/);
36
+ return parts.includes("..");
37
+ };
38
+ export const resolveAndEnsureInside = async (mount, ...parts) => {
39
+ if (!mount)
40
+ throw new Error("Mount path must be provided");
41
+ const mountResolved = path.resolve(mount);
42
+ const cleanedParts = parts.map((p) => p.replace(/^[/\\]+/, ""));
43
+ const targetResolved = path.resolve(mountResolved, ...cleanedParts);
44
+ const relative = path.relative(mountResolved, targetResolved);
45
+ if (relative === "" || !relative.startsWith(".." + path.sep) && relative !== "..") {
46
+ let cur = targetResolved;
47
+ while (cur) {
48
+ try {
49
+ await stat(cur);
50
+ break;
51
+ } catch (error) {
52
+ const parent = path.dirname(cur);
53
+ if (parent === cur)
54
+ break;
55
+ cur = parent;
56
+ }
57
+ }
58
+ const mountReal = await realpath(mountResolved);
59
+ const curReal = await realpath(cur);
60
+ if (!curReal.startsWith(mountReal)) {
61
+ throw new Error("Resolved path escapes configured mount (symlink detected)");
62
+ }
63
+ return targetResolved;
64
+ }
65
+ throw createError({
66
+ statusCode: 400,
67
+ statusMessage: "Resolved path is outside of configured mount"
68
+ });
69
+ };
@@ -1,25 +1,68 @@
1
- /// <reference types="node" />
2
- import type { ServerFile } from '../../../types';
1
+ import type { ServerFile } from '../../../types.js';
2
+ import type { H3Event, EventHandlerRequest } from 'h3';
3
3
  /**
4
- * #### Will store the file in the specified directory
4
+ * @description Will store the file in the specified directory
5
+ * @param file provide the file object
6
+ * @param fileNameOrIdLength you can pass a string or a number, if you enter a string it will be the file name, if you enter a number it will generate a unique ID
7
+ * @param filelocation provide the folder you wish to locate the file in
5
8
  * @returns file name: `${filename}`.`${fileExtension}`
6
- * @prop `file`: provide the file object
7
- * @prop `fileNameOrIdLength`: you can pass a string or a number, if you enter a string it will be the file name, if you enter a number it will generate a unique ID
8
- * @prop `filelocation`: provide the folder you wish to locate the file in
9
+ *
10
+ *
11
+ * [Documentation](https://github.com/NyllRE/nuxt-file-storage#handling-files-in-the-backend)
12
+ *
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import type { ServerFile } from "nuxt-file-storage";
17
+ *
18
+ * export default defineEventHandler(async (event) => {
19
+ * const { file } = await readBody<{ file: ServerFile }>(event);
20
+ * await storeFileLocally( file, 8, '/userFiles' );
21
+ * })
22
+ * ```
9
23
  */
10
24
  export declare const storeFileLocally: (file: ServerFile, fileNameOrIdLength: string | number, filelocation?: string) => Promise<string>;
11
25
  /**
12
- *
13
- * @param `filename`: the name of the file you want to delete
14
- * @param `filelocation`: the folder where the file is located, if it is in the root folder you can leave it empty, if it is in a subfolder you can pass the name of the subfolder with a preceding slash: `/subfolder`
26
+ * @description Get file path in the specified directory
27
+ * @param filename provide the file name (return of storeFileLocally)
28
+ * @param filelocation provide the folder you wish to locate the file in
29
+ * @returns file path: `${config.fileStorage.mount}/${filelocation}/${filename}`
30
+ */
31
+ export declare const getFileLocally: (filename: string, filelocation?: string) => string;
32
+ /**
33
+ * @description Get all files in the specified directory
34
+ * @param filelocation provide the folder you wish to locate the file in
35
+ * @returns all files in filelocation: `${config.fileStorage.mount}/${filelocation}`
36
+ */
37
+ export declare const getFilesLocally: (filelocation?: string) => Promise<string[]>;
38
+ /**
39
+ * @param filename the name of the file you want to delete
40
+ * @param filelocation the folder where the file is located, if it is in the root folder you can leave it empty, if it is in a subfolder you can pass the name of the subfolder with a preceding slash: `/subfolder`
41
+ * @example
42
+ * ```ts
43
+ * await deleteFile('/userFiles', 'requiredFile.txt')
44
+ * ```
15
45
  */
16
46
  export declare const deleteFile: (filename: string, filelocation?: string) => Promise<void>;
17
47
  /**
18
- Parses a data URL and returns an object with the binary data and the file extension.
19
- @param {string} file - The data URL
20
- @returns {{ binaryString: Buffer, ext: string }} - An object with the binary data and the file extension
48
+ * @description Parses a data URL and returns an object with the binary data and the file extension.
49
+ * @param {string} file - The data URL
50
+ * @returns {{binaryString: Buffer, ext: string}} An object with the binary data - file extension
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const { binaryString, ext } = parseDataUrl(file.content)
55
+ * ```
21
56
  */
22
57
  export declare const parseDataUrl: (file: string) => {
23
58
  binaryString: Buffer;
24
59
  ext: string;
25
60
  };
61
+ /**
62
+ * Retrieve a file as a readable stream from local storage
63
+ * @param event H3 event to set response headers
64
+ * @param filename name of the file to retrieve
65
+ * @param filelocation folder where the file is located
66
+ * @returns Readable stream of the file
67
+ */
68
+ export declare const retrieveFileLocally: (event: H3Event<EventHandlerRequest>, filename: string, filelocation?: string) => Promise<NodeJS.ReadableStream>;
@@ -0,0 +1,122 @@
1
+ import { writeFile, rm, mkdir, readdir } from "fs/promises";
2
+ import path from "path";
3
+ import {
4
+ normalizeRelative,
5
+ ensureSafeBasename,
6
+ resolveAndEnsureInside
7
+ } from "./path-safety.js";
8
+ import { createError, useRuntimeConfig } from "#imports";
9
+ import { createReadStream, promises as fsPromises } from "fs";
10
+ const getMount = () => {
11
+ try {
12
+ return useRuntimeConfig().public.fileStorage.mount;
13
+ } catch (err) {
14
+ return process.env.FILE_STORAGE_MOUNT || process.env.NUXT_FILE_STORAGE_MOUNT;
15
+ }
16
+ };
17
+ export const storeFileLocally = async (file, fileNameOrIdLength, filelocation = "") => {
18
+ const { binaryString, ext } = parseDataUrl(file.content);
19
+ const location = getMount();
20
+ if (!location)
21
+ throw new Error("fileStorage.mount is not configured");
22
+ const originalExt = file.name.toString().split(".").pop() || ext;
23
+ const safeExt = originalExt.replace(/[^a-zA-Z0-9]/g, "") || ext;
24
+ let filename;
25
+ if (typeof fileNameOrIdLength === "number") {
26
+ filename = `${generateRandomId(fileNameOrIdLength)}.${safeExt}`;
27
+ } else {
28
+ ensureSafeBasename(fileNameOrIdLength);
29
+ filename = `${fileNameOrIdLength}.${safeExt}`;
30
+ }
31
+ const normalizedFilelocation = normalizeRelative(filelocation);
32
+ const dirPath = await resolveAndEnsureInside(location, normalizedFilelocation);
33
+ await mkdir(dirPath, { recursive: true });
34
+ const targetPath = await resolveAndEnsureInside(location, normalizedFilelocation, filename);
35
+ await writeFile(targetPath, binaryString, {
36
+ flag: "w"
37
+ });
38
+ return filename;
39
+ };
40
+ export const getFileLocally = (filename, filelocation = "") => {
41
+ const location = getMount();
42
+ if (!location)
43
+ throw new Error("fileStorage.mount is not configured");
44
+ ensureSafeBasename(filename);
45
+ const normalizedFilelocation = normalizeRelative(filelocation);
46
+ const resolved = path.resolve(location, normalizedFilelocation, filename);
47
+ const mountResolved = path.resolve(location);
48
+ const relative = path.relative(mountResolved, resolved);
49
+ if (relative === "" || !relative.startsWith(".." + path.sep) && relative !== "..") {
50
+ return resolved;
51
+ }
52
+ throw createError({
53
+ statusCode: 400,
54
+ statusMessage: "Resolved path is outside of configured mount"
55
+ });
56
+ };
57
+ export const getFilesLocally = async (filelocation = "") => {
58
+ const location = getMount();
59
+ if (!location)
60
+ return [];
61
+ const normalizedFilelocation = normalizeRelative(filelocation);
62
+ const dirPath = await resolveAndEnsureInside(location, normalizedFilelocation);
63
+ return await readdir(dirPath).catch(() => []);
64
+ };
65
+ export const deleteFile = async (filename, filelocation = "") => {
66
+ const location = getMount();
67
+ if (!location)
68
+ throw new Error("fileStorage.mount is not configured");
69
+ ensureSafeBasename(filename);
70
+ const normalizedFilelocation = normalizeRelative(filelocation);
71
+ const targetPath = await resolveAndEnsureInside(location, normalizedFilelocation, filename);
72
+ await rm(targetPath);
73
+ };
74
+ const generateRandomId = (length) => {
75
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
76
+ let randomId = "";
77
+ for (let i = 0; i < length; i++) {
78
+ randomId += characters.charAt(Math.floor(Math.random() * characters.length));
79
+ }
80
+ return randomId;
81
+ };
82
+ export const parseDataUrl = (file) => {
83
+ const arr = file.split(",");
84
+ const mimeMatch = arr[0].match(/:(.*?);/);
85
+ if (!mimeMatch) {
86
+ throw new Error("Invalid data URL");
87
+ }
88
+ const mime = mimeMatch[1];
89
+ const base64String = arr[1];
90
+ const binaryString = Buffer.from(base64String, "base64");
91
+ const ext = mime.split("/")[1];
92
+ return { binaryString, ext };
93
+ };
94
+ export const retrieveFileLocally = async (event, filename, filelocation = "") => {
95
+ const filePath = getFileLocally(filename, filelocation);
96
+ let stats;
97
+ try {
98
+ stats = await fsPromises.stat(filePath);
99
+ if (!stats.isFile()) {
100
+ throw createError({ statusCode: 404, statusMessage: "Not Found" });
101
+ }
102
+ } catch (err) {
103
+ throw createError({ statusCode: 404, statusMessage: "Not Found" });
104
+ }
105
+ const ext = path.extname(filePath).slice(1).toLowerCase();
106
+ const mimeMap = {
107
+ png: "image/png",
108
+ jpg: "image/jpeg",
109
+ jpeg: "image/jpeg",
110
+ gif: "image/gif",
111
+ svg: "image/svg+xml",
112
+ pdf: "application/pdf",
113
+ txt: "text/plain",
114
+ html: "text/html",
115
+ json: "application/json"
116
+ };
117
+ const contentType = mimeMap[ext] || "application/octet-stream";
118
+ event.node.res.setHeader("Content-Type", contentType);
119
+ event.node.res.setHeader("Content-Length", String(stats.size));
120
+ event.node.res.setHeader("Content-Disposition", `inline; filename="${path.basename(filePath)}"`);
121
+ return createReadStream(filePath);
122
+ };
package/dist/types.d.mts CHANGED
@@ -1,16 +1 @@
1
-
2
- import type { ModuleOptions } from './module.js'
3
-
4
-
5
- declare module '@nuxt/schema' {
6
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
7
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
8
- }
9
-
10
- declare module 'nuxt/schema' {
11
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
12
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
13
- }
14
-
15
-
16
- export type { ClientFile, ModuleOptions, ServerFile, default } from './module.js'
1
+ export { type useRuntimeConfig } from './module.js'
package/dist/types.d.ts CHANGED
@@ -1,16 +1 @@
1
-
2
- import type { ModuleOptions } from './module'
3
-
4
-
5
- declare module '@nuxt/schema' {
6
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
7
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
8
- }
9
-
10
- declare module 'nuxt/schema' {
11
- interface NuxtConfig { ['fileStorage']?: Partial<ModuleOptions> }
12
- interface NuxtOptions { ['fileStorage']?: ModuleOptions }
13
- }
14
-
15
-
16
- export type { ClientFile, ModuleOptions, ServerFile, default } from './module'
1
+ export { type useRuntimeConfig } from './module'
package/package.json CHANGED
@@ -1,46 +1,52 @@
1
1
  {
2
- "name": "nuxt-file-storage",
3
- "version": "0.2.9",
4
- "description": "Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.",
5
- "repository": "NyllRE/nuxt-file-storage",
6
- "license": "MIT",
7
- "type": "module",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/types.d.ts",
11
- "import": "./dist/module.mjs",
12
- "require": "./dist/module.cjs"
13
- }
14
- },
15
- "main": "./dist/module.cjs",
16
- "types": "./dist/types.d.ts",
17
- "files": [
18
- "dist"
19
- ],
20
- "scripts": {
21
- "prepack": "nuxt-module-build build",
22
- "dev": "nuxi dev playground",
23
- "dev:build": "nuxi build playground",
24
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
25
- "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
26
- "lint": "eslint .",
27
- "test": "vitest run",
28
- "test:watch": "vitest watch"
29
- },
30
- "dependencies": {
31
- "@nuxt/kit": "^3.9.3",
32
- "defu": "^6.1.4"
33
- },
34
- "devDependencies": {
35
- "@nuxt/devtools": "latest",
36
- "@nuxt/eslint-config": "^0.2.0",
37
- "@nuxt/module-builder": "^0.8.3",
38
- "@nuxt/schema": "^3.9.3",
39
- "@nuxt/test-utils": "^3.9.0",
40
- "@types/node": "^20.11.5",
41
- "changelogen": "^0.5.5",
42
- "eslint": "^8.56.0",
43
- "nuxt": "^3.9.3",
44
- "vitest": "^1.0.0"
45
- }
46
- }
2
+ "name": "nuxt-file-storage",
3
+ "version": "0.3.1",
4
+ "description": "Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.",
5
+ "repository": "NyllRE/nuxt-file-storage",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types.d.ts",
11
+ "import": "./dist/module.mjs",
12
+ "require": "./dist/module.cjs"
13
+ }
14
+ },
15
+ "main": "./dist/module.cjs",
16
+ "types": "./dist/types.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "prepack": "nuxt-module-build build",
22
+ "dev": "nuxi dev playground",
23
+ "dev:build": "nuxi build playground",
24
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
25
+ "bump": "npm version patch -m \"chore(release): %s\"",
26
+ "release": "npm run lint && npm run bump && npm run prepack && changelogen && git push --follow-tags && npm publish",
27
+ "lint": "eslint .",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest watch"
30
+ },
31
+ "dependencies": {
32
+ "@nuxt/kit": "^3.15.4",
33
+ "defu": "^6.1.4"
34
+ },
35
+ "devDependencies": {
36
+ "@nuxt/devtools": "latest",
37
+ "@nuxt/eslint-config": "^0.2.0",
38
+ "@nuxt/module-builder": "^0.8.4",
39
+ "@nuxt/schema": "^3.15.4",
40
+ "@nuxt/test-utils": "^3.21.0",
41
+ "@types/node": "^20.17.19",
42
+ "@vitest/ui": "4.0.16",
43
+ "@vue/test-utils": "^2.4.6",
44
+ "changelogen": "^0.5.7",
45
+ "eslint": "^8.57.1",
46
+ "happy-dom": "^20.0.11",
47
+ "nuxt": "^3.15.4",
48
+ "playwright-core": "^1.57.0",
49
+ "typescript": "^5.9.3",
50
+ "vitest": "^4.0.16"
51
+ }
52
+ }
@@ -1,37 +0,0 @@
1
- import { writeFile, rm, mkdir } from "fs/promises";
2
- import { useRuntimeConfig } from "#imports";
3
- export const storeFileLocally = async (file, fileNameOrIdLength, filelocation = "") => {
4
- const { binaryString, ext } = parseDataUrl(file.content);
5
- const location = useRuntimeConfig().public.fileStorage.mount;
6
- const originalExt = file.name.toString().split(".").pop() || ext;
7
- const filename = typeof fileNameOrIdLength == "number" ? `${generateRandomId(fileNameOrIdLength)}.${originalExt}` : `${fileNameOrIdLength}.${originalExt}`;
8
- await mkdir(`${location}${filelocation}`, { recursive: true });
9
- await writeFile(`${location}${filelocation}/${filename}`, binaryString, {
10
- flag: "w"
11
- });
12
- return filename;
13
- };
14
- export const deleteFile = async (filename, filelocation = "") => {
15
- const location = useRuntimeConfig().public.fileStorage.mount;
16
- await rm(`${location}${filelocation}/${filename}`);
17
- };
18
- const generateRandomId = (length) => {
19
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
20
- let randomId = "";
21
- for (let i = 0; i < length; i++) {
22
- randomId += characters.charAt(Math.floor(Math.random() * characters.length));
23
- }
24
- return randomId;
25
- };
26
- export const parseDataUrl = (file) => {
27
- const arr = file.split(",");
28
- const mimeMatch = arr[0].match(/:(.*?);/);
29
- if (!mimeMatch) {
30
- throw new Error("Invalid data URL");
31
- }
32
- const mime = mimeMatch[1];
33
- const base64String = arr[1];
34
- const binaryString = Buffer.from(base64String, "base64");
35
- const ext = mime.split("/")[1];
36
- return { binaryString, ext };
37
- };
File without changes