next-tinacms-dos 0.1.0 → 0.1.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
@@ -23,8 +23,8 @@ You need some credentials provided by Digital Ocean Space to set this up properl
23
23
  Add the following variables to an `.env` file.
24
24
 
25
25
  ```
26
- NEXT_PUBLIC_SPACE_ENDPOINT=<Your Digital Ocean Space Endpoint>
27
- NEXT_PUBLIC_SPACE_NAME=<Your Digital Ocean Space Name>
26
+ NEXT_PUBLIC_SPACE_ENDPOINT=<Your Digital Ocean Space Endpoint: ex. https://fra1.digitaloceanspaces.com>
27
+ NEXT_PUBLIC_SPACE_NAME=<Your Digital Ocean Space Name: ex. my-space>
28
28
  NEXT_PUBLIC_SPACE_KEY=<Your Digital Ocean Space access key>
29
29
  SPACE_SECRET_KEY=<Your Digital Ocean Space access secret>
30
30
  ```
@@ -41,7 +41,6 @@ This is also where we can update our `mediaOptions` on the cms object.
41
41
  import dynamic from "next/dynamic";
42
42
  import { TinaEditProvider } from "tinacms/dist/edit-state";
43
43
  import { Layout } from "../components/layout";
44
- import { TinaCloudDOSMediaStore } from "next-tinacms-dos";
45
44
  const TinaCMS = dynamic(() => import("tinacms"), { ssr: false });
46
45
 
47
46
  const App = ({ Component, pageProps }) => {
@@ -86,7 +85,7 @@ const App = ({ Component, pageProps }) => {
86
85
 
87
86
  ## Set up API routes
88
87
 
89
- Set up a new API route in the `pages` directory of your Next.js app, e.g. `pages/api/media`.
88
+ Set up a new API route in the `pages` directory of your Next.js app, e.g. `pages/api/dos/[...media].ts`.
90
89
  Then add a new catch all API route for media.
91
90
 
92
91
  Call `createMediaHandler` to set up routes and connect your instance of the Media Store to your Digital Ocean Space.
@@ -97,7 +96,7 @@ The `authorized` key will make it so only authorized users within Tina Cloud can
97
96
 
98
97
 
99
98
  ```
100
- //[...media].tsx
99
+ // pages/api/dos/[...media].ts
101
100
 
102
101
  import {
103
102
  mediaHandlerConfig,
@@ -0,0 +1,172 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
8
+ var __export = (target, all) => {
9
+ __markAsModule(target);
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __reExport = (target, module2, desc) => {
14
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
15
+ for (let key of __getOwnPropNames(module2))
16
+ if (!__hasOwnProp.call(target, key) && key !== "default")
17
+ __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
18
+ }
19
+ return target;
20
+ };
21
+ var __toModule = (module2) => {
22
+ return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
23
+ };
24
+
25
+ // src/handlers.ts
26
+ __export(exports, {
27
+ createMediaHandler: () => createMediaHandler,
28
+ mediaHandlerConfig: () => mediaHandlerConfig
29
+ });
30
+ var import_client_s3 = __toModule(require("@aws-sdk/client-s3"));
31
+ var import_path = __toModule(require("path"));
32
+ var import_fs = __toModule(require("fs"));
33
+ var import_multer = __toModule(require("multer"));
34
+ var import_util = __toModule(require("util"));
35
+ var mediaHandlerConfig = {
36
+ api: {
37
+ bodyParser: false
38
+ }
39
+ };
40
+ var createMediaHandler = (config, options) => {
41
+ const client = new import_client_s3.S3Client(config.config);
42
+ const bucket = config.bucket;
43
+ const cdnUrl = (options == null ? void 0 : options.cdnUrl) || config.config.endpoint.toString().replace(/http(s|):\/\//i, `https://${bucket}.`);
44
+ return async (req, res) => {
45
+ const isAuthorized = await config.authorized(req, res);
46
+ if (!isAuthorized) {
47
+ res.status(401).json({ message: "sorry this user is unauthorized" });
48
+ return;
49
+ }
50
+ switch (req.method) {
51
+ case "GET":
52
+ return listMedia(req, res, client, bucket, cdnUrl);
53
+ case "POST":
54
+ return uploadMedia(req, res, client, bucket);
55
+ case "DELETE":
56
+ return deleteAsset(req, res, client, bucket);
57
+ default:
58
+ res.end(404);
59
+ }
60
+ };
61
+ };
62
+ async function uploadMedia(req, res, client, bucket) {
63
+ const upload = (0, import_util.promisify)((0, import_multer.default)({
64
+ storage: import_multer.default.diskStorage({
65
+ directory: (req2, file, cb) => {
66
+ cb(null, "/tmp");
67
+ },
68
+ filename: (req2, file, cb) => {
69
+ cb(null, file.originalname);
70
+ }
71
+ })
72
+ }).single("file"));
73
+ await upload(req, res);
74
+ const { directory } = req.body;
75
+ let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
76
+ if (prefix)
77
+ prefix = prefix + "/";
78
+ const filePath = req.file.path;
79
+ const blob = import_fs.default.readFileSync(filePath);
80
+ const params = {
81
+ Bucket: bucket,
82
+ Key: prefix + import_path.default.basename(filePath),
83
+ Body: blob,
84
+ ACL: "public-read"
85
+ };
86
+ const command = new import_client_s3.PutObjectCommand(params);
87
+ const result = await client.send(command);
88
+ res.json(result);
89
+ }
90
+ async function listMedia(req, res, client, bucket, cdnUrl) {
91
+ var _a;
92
+ try {
93
+ const {
94
+ directory = "",
95
+ limit = 500,
96
+ offset
97
+ } = req.query;
98
+ let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
99
+ if (prefix)
100
+ prefix = prefix + "/";
101
+ const params = {
102
+ Bucket: bucket,
103
+ Delimiter: "/",
104
+ Prefix: prefix,
105
+ Marker: offset == null ? void 0 : offset.toString(),
106
+ MaxKeys: directory && !offset ? +limit + 1 : +limit
107
+ };
108
+ const command = new import_client_s3.ListObjectsCommand(params);
109
+ const response = await client.send(command);
110
+ const items = [];
111
+ (_a = response.CommonPrefixes) == null ? void 0 : _a.forEach(({ Prefix }) => items.push({
112
+ id: Prefix,
113
+ type: "dir",
114
+ filename: import_path.default.basename(Prefix),
115
+ directory: import_path.default.dirname(Prefix)
116
+ }));
117
+ items.push(...(response.Contents || []).filter((file) => file.Key !== prefix).map(getDOSToTinaFunc(cdnUrl)));
118
+ res.json({
119
+ items,
120
+ offset: response.NextMarker
121
+ });
122
+ } catch (e) {
123
+ res.status(500);
124
+ const message = findErrorMessage(e);
125
+ res.json({ e: message });
126
+ }
127
+ }
128
+ var findErrorMessage = (e) => {
129
+ if (typeof e == "string")
130
+ return e;
131
+ if (e.message)
132
+ return e.message;
133
+ if (e.error && e.error.message)
134
+ return e.error.message;
135
+ return "an error occurred";
136
+ };
137
+ async function deleteAsset(req, res, client, bucket) {
138
+ const { media } = req.query;
139
+ const [, objectKey] = media;
140
+ const params = {
141
+ Bucket: bucket,
142
+ Key: objectKey
143
+ };
144
+ const command = new import_client_s3.DeleteObjectCommand(params);
145
+ try {
146
+ const data = await client.send(command);
147
+ res.json(data);
148
+ } catch (err) {
149
+ res.status(500).json({
150
+ message: err.message || "Something went wrong"
151
+ });
152
+ }
153
+ }
154
+ function getDOSToTinaFunc(cdnUrl) {
155
+ return function dosToTina(file) {
156
+ const filename = import_path.default.basename(file.Key);
157
+ const directory = import_path.default.dirname(file.Key) + "/";
158
+ return {
159
+ id: file.Key,
160
+ filename,
161
+ directory,
162
+ src: cdnUrl + "/" + file.Key,
163
+ previewSrc: cdnUrl + "/" + file.Key,
164
+ type: "file"
165
+ };
166
+ };
167
+ }
168
+ // Annotate the CommonJS export names for ESM import in node:
169
+ 0 && (module.exports = {
170
+ createMediaHandler,
171
+ mediaHandlerConfig
172
+ });
package/dist/index.js CHANGED
@@ -1,172 +1,137 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
8
- var __export = (target, all) => {
9
- __markAsModule(target);
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __reExport = (target, module2, desc) => {
14
- if (module2 && typeof module2 === "object" || typeof module2 === "function") {
15
- for (let key of __getOwnPropNames(module2))
16
- if (!__hasOwnProp.call(target, key) && key !== "default")
17
- __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
18
- }
19
- return target;
20
- };
21
- var __toModule = (module2) => {
22
- return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
23
- };
24
-
25
- // src/handlers.ts
26
- __export(exports, {
27
- createMediaHandler: () => createMediaHandler,
28
- mediaHandlerConfig: () => mediaHandlerConfig
29
- });
30
- var import_client_s3 = __toModule(require("@aws-sdk/client-s3"));
31
- var import_path = __toModule(require("path"));
32
- var import_fs = __toModule(require("fs"));
33
- var import_multer = __toModule(require("multer"));
34
- var import_util = __toModule(require("util"));
35
- var mediaHandlerConfig = {
36
- api: {
37
- bodyParser: false
38
- }
39
- };
40
- var createMediaHandler = (config, options) => {
41
- const client = new import_client_s3.S3Client(config.config);
42
- const bucket = config.bucket;
43
- const cdnUrl = (options == null ? void 0 : options.cdnUrl) || config.config.endpoint.toString().replace(/http(s|):\/\//i, `https://${bucket}.`);
44
- return async (req, res) => {
45
- const isAuthorized = await config.authorized(req, res);
46
- if (!isAuthorized) {
47
- res.status(401).json({ message: "sorry this user is unauthorized" });
48
- return;
49
- }
50
- switch (req.method) {
51
- case "GET":
52
- return listMedia(req, res, client, bucket, cdnUrl);
53
- case "POST":
54
- return uploadMedia(req, res, client, bucket);
55
- case "DELETE":
56
- return deleteAsset(req, res, client, bucket);
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@tinacms/toolkit")) : typeof define === "function" && define.amd ? define(["exports", "@tinacms/toolkit"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["next-tinacms-dos"] = {}, global.NOOP));
3
+ })(this, function(exports2, toolkit) {
4
+ "use strict";
5
+ const E_DEFAULT = new toolkit.MediaListError({
6
+ title: "An Error Occurred",
7
+ message: "Something went wrong fetching your media from Digital Ocean Space.",
8
+ docsLink: "https://tina.io/packages/next-tinacms-dos"
9
+ });
10
+ const E_UNAUTHORIZED = new toolkit.MediaListError({
11
+ title: "Unauthorized",
12
+ message: "You don't have access to this resource.",
13
+ docsLink: "https://tina.io/packages/next-tinacms-dos"
14
+ });
15
+ const E_CONFIG = new toolkit.MediaListError({
16
+ title: "Missing Credentials",
17
+ message: "Unable to connect to Digital Ocean Space because one or more environment variables are missing.",
18
+ docsLink: "https://tina.io/docs/media-dos/"
19
+ });
20
+ const E_KEY_FAIL = new toolkit.MediaListError({
21
+ title: "Bad Credentials",
22
+ message: "Unable to connect to Digital Ocean Space because one or more environment variables are misconfigured.",
23
+ docsLink: "https://tina.io/docs/media-dos/"
24
+ });
25
+ const E_BAD_ROUTE = new toolkit.MediaListError({
26
+ title: "Bad Route",
27
+ message: "The Digital Ocean Space API route is missing or misconfigured.",
28
+ docsLink: "https://tina.io/packages/next-tinacms-dos/#set-up-api-routes"
29
+ });
30
+ const interpretErrorMessage = (message) => {
31
+ switch (message) {
32
+ case "Must supply cloud_name":
33
+ case "Must supply api_key":
34
+ case "Must supply api_secret":
35
+ return E_CONFIG;
36
+ case "unknown api_key":
37
+ return E_KEY_FAIL;
57
38
  default:
58
- res.end(404);
39
+ return E_DEFAULT;
59
40
  }
60
41
  };
61
- };
62
- async function uploadMedia(req, res, client, bucket) {
63
- const upload = (0, import_util.promisify)((0, import_multer.default)({
64
- storage: import_multer.default.diskStorage({
65
- directory: (req2, file, cb) => {
66
- cb(null, "/tmp");
67
- },
68
- filename: (req2, file, cb) => {
69
- cb(null, file.originalname);
42
+ class DOSMediaStore {
43
+ constructor() {
44
+ this.fetchFunction = (input, init) => {
45
+ return fetch(input, init);
46
+ };
47
+ this.accept = "text/*, application/*, image/*";
48
+ this.previewSrc = (publicId) => {
49
+ if (typeof publicId === "string")
50
+ return publicId;
51
+ return publicId.previewSrc;
52
+ };
53
+ this.parse = (img) => {
54
+ return img.src;
55
+ };
56
+ }
57
+ async persist(media) {
58
+ let newFiles = [];
59
+ for (const item of media) {
60
+ const { file, directory } = item;
61
+ const formData = new FormData();
62
+ formData.append("file", file);
63
+ formData.append("directory", directory);
64
+ formData.append("filename", file.name);
65
+ const res = await this.fetchFunction(`/api/dos/media`, {
66
+ method: "POST",
67
+ body: formData
68
+ });
69
+ if (res.status != 200) {
70
+ const responseData = await res.json();
71
+ throw new Error(responseData.message);
72
+ }
73
+ const fileRes = await res.json();
74
+ await new Promise((resolve) => {
75
+ setTimeout(resolve, 2e3);
76
+ });
77
+ const parsedRes = {
78
+ type: "file",
79
+ id: fileRes.public_id,
80
+ filename: fileRes.original_filename,
81
+ directory: "/",
82
+ previewSrc: fileRes.url
83
+ };
84
+ newFiles.push(parsedRes);
70
85
  }
71
- })
72
- }).single("file"));
73
- await upload(req, res);
74
- const { directory } = req.body;
75
- let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
76
- if (prefix)
77
- prefix = prefix + "/";
78
- const filePath = req.file.path;
79
- const blob = import_fs.default.readFileSync(filePath);
80
- const params = {
81
- Bucket: bucket,
82
- Key: prefix + import_path.default.basename(filePath),
83
- Body: blob,
84
- ACL: "public-read"
85
- };
86
- const command = new import_client_s3.PutObjectCommand(params);
87
- const result = await client.send(command);
88
- res.json(result);
89
- }
90
- async function listMedia(req, res, client, bucket, cdnUrl) {
91
- var _a;
92
- try {
93
- const {
94
- directory = "",
95
- limit = 500,
96
- offset
97
- } = req.query;
98
- let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
99
- if (prefix)
100
- prefix = prefix + "/";
101
- const params = {
102
- Bucket: bucket,
103
- Delimiter: "/",
104
- Prefix: prefix,
105
- Marker: offset == null ? void 0 : offset.toString(),
106
- MaxKeys: directory && !offset ? +limit + 1 : +limit
107
- };
108
- const command = new import_client_s3.ListObjectsCommand(params);
109
- const response = await client.send(command);
110
- const items = [];
111
- (_a = response.CommonPrefixes) == null ? void 0 : _a.forEach(({ Prefix }) => items.push({
112
- id: Prefix,
113
- type: "dir",
114
- filename: import_path.default.basename(Prefix),
115
- directory: import_path.default.dirname(Prefix)
116
- }));
117
- items.push(...(response.Contents || []).filter((file) => file.Key !== prefix).map(getDOSToTinaFunc(cdnUrl)));
118
- res.json({
119
- items,
120
- offset: response.NextMarker
121
- });
122
- } catch (e) {
123
- res.status(500);
124
- const message = findErrorMessage(e);
125
- res.json({ e: message });
86
+ return newFiles;
87
+ }
88
+ async delete(media) {
89
+ await this.fetchFunction(`/api/dos/media/${encodeURIComponent(media.id)}`, {
90
+ method: "DELETE"
91
+ });
92
+ }
93
+ async list(options) {
94
+ const query = this.buildQuery(options);
95
+ const response = await this.fetchFunction("/api/dos/media" + query);
96
+ if (response.status == 401) {
97
+ throw E_UNAUTHORIZED;
98
+ }
99
+ if (response.status == 404) {
100
+ throw E_BAD_ROUTE;
101
+ }
102
+ if (response.status >= 500) {
103
+ const { e } = await response.json();
104
+ const error = interpretErrorMessage(e);
105
+ throw error;
106
+ }
107
+ const { items, offset } = await response.json();
108
+ return {
109
+ items: items.map((item) => item),
110
+ nextOffset: offset
111
+ };
112
+ }
113
+ buildQuery(options) {
114
+ const params = Object.keys(options).filter((key) => options[key] !== "" && options[key] !== void 0).map((key) => `${key}=${options[key]}`).join("&");
115
+ return `?${params}`;
116
+ }
126
117
  }
127
- }
128
- var findErrorMessage = (e) => {
129
- if (typeof e == "string")
130
- return e;
131
- if (e.message)
132
- return e.message;
133
- if (e.error && e.error.message)
134
- return e.error.message;
135
- return "an error occurred";
136
- };
137
- async function deleteAsset(req, res, client, bucket) {
138
- const { media } = req.query;
139
- const [, objectKey] = media;
140
- const params = {
141
- Bucket: bucket,
142
- Key: objectKey
143
- };
144
- const command = new import_client_s3.DeleteObjectCommand(params);
145
- try {
146
- const data = await client.send(command);
147
- res.json(data);
148
- } catch (err) {
149
- res.status(500).json({
150
- message: err.message || "Something went wrong"
151
- });
118
+ class TinaCloudDOSMediaStore extends DOSMediaStore {
119
+ constructor(client) {
120
+ super();
121
+ this.client = client;
122
+ this.fetchFunction = async (input, init) => {
123
+ try {
124
+ const url = input.toString();
125
+ const query = `${url.includes("?") ? "&" : "?"}clientID=${client.clientId}`;
126
+ const res = client.fetchWithToken(url + query, init);
127
+ return res;
128
+ } catch (error) {
129
+ console.error(error);
130
+ }
131
+ };
132
+ }
152
133
  }
153
- }
154
- function getDOSToTinaFunc(cdnUrl) {
155
- return function dosToTina(file) {
156
- const filename = import_path.default.basename(file.Key);
157
- const directory = import_path.default.dirname(file.Key) + "/";
158
- return {
159
- id: file.Key,
160
- filename,
161
- directory,
162
- src: cdnUrl + "/" + file.Key,
163
- previewSrc: cdnUrl + "/" + file.Key,
164
- type: "file"
165
- };
166
- };
167
- }
168
- // Annotate the CommonJS export names for ESM import in node:
169
- 0 && (module.exports = {
170
- createMediaHandler,
171
- mediaHandlerConfig
134
+ exports2.DOSMediaStore = DOSMediaStore;
135
+ exports2.TinaCloudDOSMediaStore = TinaCloudDOSMediaStore;
136
+ Object.defineProperties(exports2, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
172
137
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-tinacms-dos",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist"
@@ -21,8 +21,8 @@
21
21
  "multer": "1.4.2"
22
22
  },
23
23
  "devDependencies": {
24
- "@tinacms/toolkit": "0.57.0",
25
- "@tinacms/scripts": "0.51.0",
24
+ "@tinacms/toolkit": "0.57.1",
25
+ "@tinacms/scripts": "0.51.1",
26
26
  "@types/crypto-js": "^3.1.47",
27
27
  "@types/js-cookie": "^2.2.6",
28
28
  "@types/node": "^13.13.1",
@@ -32,7 +32,7 @@
32
32
  "react": "17.0.2",
33
33
  "react-dom": "17.0.2",
34
34
  "styled-components": "^5.2.0",
35
- "tinacms": "0.0.0-20220818181559",
35
+ "tinacms": "0.69.2",
36
36
  "typescript": "4.3.5"
37
37
  },
38
38
  "peerDependencies": {
@@ -41,7 +41,7 @@
41
41
  "react-dom": ">=16.14",
42
42
  "react-is": "^16.13.1 || <18.0.0",
43
43
  "styled-components": "*",
44
- "tinacms": ">=0.0.0-20220818181559"
44
+ "tinacms": ">=0.50.0"
45
45
  },
46
46
  "publishConfig": {
47
47
  "registry": "https://registry.npmjs.org"