@interware/content-generator 0.4.2 → 0.4.4

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.
@@ -1,13 +1,13 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { useRBAC, useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
3
- import { SubNav, SubNavHeader, SubNavSections, SubNavSection, SubNavLink, Box, Table, Thead, Tr, Th, Typography, Flex, Tbody, Td, Button, LinkButton, Dialog, Grid, Field, Textarea, TextInput, Combobox, ComboboxOption, Modal, Card, CardHeader, CardCheckbox, CardAsset, CardBody, CardContent, CardTitle, CardSubtitle, CardBadge, CardAction, IconButton, DesignSystemProvider } from "@strapi/design-system";
3
+ import { SubNav, SubNavHeader, SubNavSections, SubNavSection, SubNavLink, Box, Table, Thead, Tr, Th, Typography, Flex, Tbody, Td, Button, LinkButton, Dialog, Grid, Field, Textarea, TextInput, Combobox, ComboboxOption, Modal, Card, CardHeader, CardCheckbox, CardAsset, CardBody, CardContent, CardTitle, CardSubtitle, CardBadge, CardAction, IconButton, Loader, DesignSystemProvider } from "@strapi/design-system";
4
4
  import { useLocation, useNavigate, Routes, Route } from "react-router-dom";
5
- import { p as pluginPermissions, c as closeDialog, o as openDialog, P as PLUGIN_ID } from "./index-DL56cMMt.mjs";
5
+ import { p as pluginPermissions, c as closeDialog, o as openDialog, P as PLUGIN_ID } from "./index-Dyq8dxMX.mjs";
6
6
  import { useIntl } from "react-intl";
7
7
  import { useCallback, memo, useMemo, useState, useRef, useEffect, createContext, useContext } from "react";
8
8
  import { useDispatch, useSelector } from "react-redux";
9
9
  import { Play, ExternalLink, Image as Image$1, Download, Trash, PlusCircle } from "@strapi/icons";
10
- import { g as getTranslation } from "./getTranslation-YJf3i4B7.mjs";
10
+ import { g as getTranslation } from "./getTranslation-B9ND5RaH.mjs";
11
11
  import { useFormik } from "formik";
12
12
  import * as Yup from "yup";
13
13
  const links = [
@@ -1000,8 +1000,9 @@ const ImagesProvider = ({ children }) => {
1000
1000
  const { openDialog: openDialog2, closeDialog: closeDialog2 } = useDialog("dialog-delete-image");
1001
1001
  const { formatMessage } = useIntl();
1002
1002
  const { toggleNotification } = useNotification();
1003
+ const { post } = useFetchClient();
1003
1004
  const [images, setImages] = useState([]);
1004
- const [loading, setLoading] = useState(false);
1005
+ const [downloadingId, setDownloadingId] = useState(null);
1005
1006
  const [open, setOpen] = useState(false);
1006
1007
  const currentImageRef = useRef();
1007
1008
  const fetchImages = useCallback(async () => {
@@ -1067,41 +1068,68 @@ const ImagesProvider = ({ children }) => {
1067
1068
  return;
1068
1069
  }
1069
1070
  }, [openDialog2, setOpen]);
1070
- const downloadImage = (url, filename) => {
1071
- const encodedFilename = encodeURIComponent(filename);
1072
- const separator = url.includes("?") ? "&" : "?";
1073
- const finalUrl = `${url}${separator}response-content-disposition=attachment;filename="${encodedFilename}"`;
1074
- const link = document.createElement("a");
1075
- link.href = finalUrl;
1076
- link.target = "_blank";
1077
- link.rel = "noopener";
1078
- document.body.appendChild(link);
1079
- link.click();
1080
- document.body.removeChild(link);
1071
+ const downloadImage = async (url, filename) => {
1072
+ if (!url) return;
1073
+ const absoluteUrl = url.startsWith("/uploads") ? `${window.location.origin}${url}` : url;
1074
+ try {
1075
+ const { data } = await post(
1076
+ "/strapi-content-generator-gemini/download-file",
1077
+ { url: absoluteUrl }
1078
+ );
1079
+ const { base64, contentType } = data;
1080
+ const binary = atob(base64);
1081
+ const bytes = new Uint8Array(binary.length);
1082
+ for (let i = 0; i < binary.length; i++) {
1083
+ bytes[i] = binary.charCodeAt(i);
1084
+ }
1085
+ const blob = new Blob([bytes], { type: contentType });
1086
+ const blobUrl = window.URL.createObjectURL(blob);
1087
+ const link = document.createElement("a");
1088
+ link.href = blobUrl;
1089
+ link.download = filename;
1090
+ document.body.appendChild(link);
1091
+ link.click();
1092
+ document.body.removeChild(link);
1093
+ window.URL.revokeObjectURL(blobUrl);
1094
+ } catch (error) {
1095
+ console.error("Download error:", error);
1096
+ const link = document.createElement("a");
1097
+ link.href = absoluteUrl;
1098
+ link.target = "_blank";
1099
+ link.rel = "noopener noreferrer";
1100
+ document.body.appendChild(link);
1101
+ link.click();
1102
+ document.body.removeChild(link);
1103
+ }
1081
1104
  };
1082
1105
  const handleDownload = async (selectedKeys) => {
1083
1106
  const image = currentImageRef.current;
1084
1107
  if (!image) return;
1085
1108
  setOpen(false);
1086
- const keys = Array.isArray(selectedKeys) ? selectedKeys : Array.from(selectedKeys);
1087
- const extension = image.ext?.replace(".", "") ?? "jpg";
1088
- const formats = image.formats;
1089
- const items = keys.map((key) => {
1090
- if (key === "principal") {
1109
+ setDownloadingId(image.documentId);
1110
+ try {
1111
+ const keys = Array.isArray(selectedKeys) ? selectedKeys : Array.from(selectedKeys);
1112
+ const extension = image.ext?.replace(".", "") ?? "jpg";
1113
+ const formats = image.formats;
1114
+ const items = keys.map((key) => {
1115
+ if (key === "principal") {
1116
+ return {
1117
+ url: image.url,
1118
+ filename: `image-principal-${image.documentId}.${extension}`
1119
+ };
1120
+ }
1121
+ const formatImage = formats?.[key];
1122
+ if (!formatImage) return null;
1091
1123
  return {
1092
- url: image.url,
1093
- filename: `image-principal-${image.documentId}.${extension}`
1124
+ url: formatImage.url,
1125
+ filename: `image-${key}-${image.documentId}.${extension}`
1094
1126
  };
1127
+ }).filter(Boolean);
1128
+ for (const item of items) {
1129
+ await downloadImage(item.url, item.filename);
1095
1130
  }
1096
- const formatImage = formats?.[key];
1097
- if (!formatImage) return null;
1098
- return {
1099
- url: formatImage.url,
1100
- filename: `image-${key}-${image.documentId}.${extension}`
1101
- };
1102
- }).filter(Boolean);
1103
- for (const item of items) {
1104
- await downloadImage(item.url, item.filename);
1131
+ } finally {
1132
+ setDownloadingId(null);
1105
1133
  }
1106
1134
  };
1107
1135
  useEffect(() => {
@@ -1130,7 +1158,7 @@ const ImagesProvider = ({ children }) => {
1130
1158
  ] })
1131
1159
  }
1132
1160
  ),
1133
- /* @__PURE__ */ jsx(ImagesContext.Provider, { value: { images, onClickAction }, children })
1161
+ /* @__PURE__ */ jsx(ImagesContext.Provider, { value: { images, downloadingId, onClickAction }, children })
1134
1162
  ] });
1135
1163
  };
1136
1164
  const useImages = () => {
@@ -1141,7 +1169,8 @@ const useImages = () => {
1141
1169
  return context;
1142
1170
  };
1143
1171
  const Image = ({ image }) => {
1144
- const { onClickAction } = useImages();
1172
+ const { onClickAction, downloadingId } = useImages();
1173
+ const isDownloading = downloadingId === image.documentId;
1145
1174
  return /* @__PURE__ */ jsxs(Card, { id: image.id, children: [
1146
1175
  /* @__PURE__ */ jsxs(CardHeader, { children: [
1147
1176
  /* @__PURE__ */ jsxs(CardAction, { position: "end", children: [
@@ -1149,10 +1178,11 @@ const Image = ({ image }) => {
1149
1178
  IconButton,
1150
1179
  {
1151
1180
  label: "Descargar",
1181
+ disabled: isDownloading,
1152
1182
  onClick: () => {
1153
1183
  onClickAction(image, "download");
1154
1184
  },
1155
- children: /* @__PURE__ */ jsx(Download, {})
1185
+ children: isDownloading ? /* @__PURE__ */ jsx(Loader, { small: true }) : /* @__PURE__ */ jsx(Download, {})
1156
1186
  }
1157
1187
  ),
1158
1188
  /* @__PURE__ */ jsx(
@@ -4,12 +4,12 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const admin = require("@strapi/strapi/admin");
5
5
  const designSystem = require("@strapi/design-system");
6
6
  const reactRouterDom = require("react-router-dom");
7
- const index = require("./index-DIbmEmw5.js");
7
+ const index = require("./index-DGPqyrzz.js");
8
8
  const reactIntl = require("react-intl");
9
9
  const react = require("react");
10
10
  const reactRedux = require("react-redux");
11
11
  const icons = require("@strapi/icons");
12
- const getTranslation = require("./getTranslation-BODOQcEi.js");
12
+ const getTranslation = require("./getTranslation-DNhP1INv.js");
13
13
  const formik = require("formik");
14
14
  const Yup = require("yup");
15
15
  function _interopNamespace(e) {
@@ -1020,8 +1020,9 @@ const ImagesProvider = ({ children }) => {
1020
1020
  const { openDialog, closeDialog } = useDialog("dialog-delete-image");
1021
1021
  const { formatMessage } = reactIntl.useIntl();
1022
1022
  const { toggleNotification } = admin.useNotification();
1023
+ const { post } = admin.useFetchClient();
1023
1024
  const [images, setImages] = react.useState([]);
1024
- const [loading, setLoading] = react.useState(false);
1025
+ const [downloadingId, setDownloadingId] = react.useState(null);
1025
1026
  const [open, setOpen] = react.useState(false);
1026
1027
  const currentImageRef = react.useRef();
1027
1028
  const fetchImages = react.useCallback(async () => {
@@ -1087,41 +1088,68 @@ const ImagesProvider = ({ children }) => {
1087
1088
  return;
1088
1089
  }
1089
1090
  }, [openDialog, setOpen]);
1090
- const downloadImage = (url, filename) => {
1091
- const encodedFilename = encodeURIComponent(filename);
1092
- const separator = url.includes("?") ? "&" : "?";
1093
- const finalUrl = `${url}${separator}response-content-disposition=attachment;filename="${encodedFilename}"`;
1094
- const link = document.createElement("a");
1095
- link.href = finalUrl;
1096
- link.target = "_blank";
1097
- link.rel = "noopener";
1098
- document.body.appendChild(link);
1099
- link.click();
1100
- document.body.removeChild(link);
1091
+ const downloadImage = async (url, filename) => {
1092
+ if (!url) return;
1093
+ const absoluteUrl = url.startsWith("/uploads") ? `${window.location.origin}${url}` : url;
1094
+ try {
1095
+ const { data } = await post(
1096
+ "/strapi-content-generator-gemini/download-file",
1097
+ { url: absoluteUrl }
1098
+ );
1099
+ const { base64, contentType } = data;
1100
+ const binary = atob(base64);
1101
+ const bytes = new Uint8Array(binary.length);
1102
+ for (let i = 0; i < binary.length; i++) {
1103
+ bytes[i] = binary.charCodeAt(i);
1104
+ }
1105
+ const blob = new Blob([bytes], { type: contentType });
1106
+ const blobUrl = window.URL.createObjectURL(blob);
1107
+ const link = document.createElement("a");
1108
+ link.href = blobUrl;
1109
+ link.download = filename;
1110
+ document.body.appendChild(link);
1111
+ link.click();
1112
+ document.body.removeChild(link);
1113
+ window.URL.revokeObjectURL(blobUrl);
1114
+ } catch (error) {
1115
+ console.error("Download error:", error);
1116
+ const link = document.createElement("a");
1117
+ link.href = absoluteUrl;
1118
+ link.target = "_blank";
1119
+ link.rel = "noopener noreferrer";
1120
+ document.body.appendChild(link);
1121
+ link.click();
1122
+ document.body.removeChild(link);
1123
+ }
1101
1124
  };
1102
1125
  const handleDownload = async (selectedKeys) => {
1103
1126
  const image = currentImageRef.current;
1104
1127
  if (!image) return;
1105
1128
  setOpen(false);
1106
- const keys = Array.isArray(selectedKeys) ? selectedKeys : Array.from(selectedKeys);
1107
- const extension = image.ext?.replace(".", "") ?? "jpg";
1108
- const formats = image.formats;
1109
- const items = keys.map((key) => {
1110
- if (key === "principal") {
1129
+ setDownloadingId(image.documentId);
1130
+ try {
1131
+ const keys = Array.isArray(selectedKeys) ? selectedKeys : Array.from(selectedKeys);
1132
+ const extension = image.ext?.replace(".", "") ?? "jpg";
1133
+ const formats = image.formats;
1134
+ const items = keys.map((key) => {
1135
+ if (key === "principal") {
1136
+ return {
1137
+ url: image.url,
1138
+ filename: `image-principal-${image.documentId}.${extension}`
1139
+ };
1140
+ }
1141
+ const formatImage = formats?.[key];
1142
+ if (!formatImage) return null;
1111
1143
  return {
1112
- url: image.url,
1113
- filename: `image-principal-${image.documentId}.${extension}`
1144
+ url: formatImage.url,
1145
+ filename: `image-${key}-${image.documentId}.${extension}`
1114
1146
  };
1147
+ }).filter(Boolean);
1148
+ for (const item of items) {
1149
+ await downloadImage(item.url, item.filename);
1115
1150
  }
1116
- const formatImage = formats?.[key];
1117
- if (!formatImage) return null;
1118
- return {
1119
- url: formatImage.url,
1120
- filename: `image-${key}-${image.documentId}.${extension}`
1121
- };
1122
- }).filter(Boolean);
1123
- for (const item of items) {
1124
- await downloadImage(item.url, item.filename);
1151
+ } finally {
1152
+ setDownloadingId(null);
1125
1153
  }
1126
1154
  };
1127
1155
  react.useEffect(() => {
@@ -1150,7 +1178,7 @@ const ImagesProvider = ({ children }) => {
1150
1178
  ] })
1151
1179
  }
1152
1180
  ),
1153
- /* @__PURE__ */ jsxRuntime.jsx(ImagesContext.Provider, { value: { images, onClickAction }, children })
1181
+ /* @__PURE__ */ jsxRuntime.jsx(ImagesContext.Provider, { value: { images, downloadingId, onClickAction }, children })
1154
1182
  ] });
1155
1183
  };
1156
1184
  const useImages = () => {
@@ -1161,7 +1189,8 @@ const useImages = () => {
1161
1189
  return context;
1162
1190
  };
1163
1191
  const Image = ({ image }) => {
1164
- const { onClickAction } = useImages();
1192
+ const { onClickAction, downloadingId } = useImages();
1193
+ const isDownloading = downloadingId === image.documentId;
1165
1194
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Card, { id: image.id, children: [
1166
1195
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.CardHeader, { children: [
1167
1196
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.CardAction, { position: "end", children: [
@@ -1169,10 +1198,11 @@ const Image = ({ image }) => {
1169
1198
  designSystem.IconButton,
1170
1199
  {
1171
1200
  label: "Descargar",
1201
+ disabled: isDownloading,
1172
1202
  onClick: () => {
1173
1203
  onClickAction(image, "download");
1174
1204
  },
1175
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {})
1205
+ children: isDownloading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true }) : /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {})
1176
1206
  }
1177
1207
  ),
1178
1208
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -7,8 +7,8 @@ const react = require("react");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const formik = require("formik");
9
9
  const Yup = require("yup");
10
- const index = require("./index-DIbmEmw5.js");
11
- const getTranslation = require("./getTranslation-BODOQcEi.js");
10
+ const index = require("./index-DGPqyrzz.js");
11
+ const getTranslation = require("./getTranslation-DNhP1INv.js");
12
12
  function _interopNamespace(e) {
13
13
  if (e && e.__esModule) return e;
14
14
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -5,8 +5,8 @@ import { useCallback, useState, useEffect } from "react";
5
5
  import { Box, Grid, Field, Flex, Button, DesignSystemProvider, Typography } from "@strapi/design-system";
6
6
  import { Formik } from "formik";
7
7
  import * as Yup from "yup";
8
- import { P as PLUGIN_ID } from "./index-DL56cMMt.mjs";
9
- import { g as getTranslation } from "./getTranslation-YJf3i4B7.mjs";
8
+ import { P as PLUGIN_ID } from "./index-Dyq8dxMX.mjs";
9
+ import { g as getTranslation } from "./getTranslation-B9ND5RaH.mjs";
10
10
  const useSettings = () => {
11
11
  const { get, post } = useFetchClient();
12
12
  const getSettings = useCallback(
@@ -1,4 +1,4 @@
1
- import { P as PLUGIN_ID } from "./index-DL56cMMt.mjs";
1
+ import { P as PLUGIN_ID } from "./index-Dyq8dxMX.mjs";
2
2
  const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
3
3
  export {
4
4
  getTranslation as g
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
- const index = require("./index-DIbmEmw5.js");
2
+ const index = require("./index-DGPqyrzz.js");
3
3
  const getTranslation = (id) => `${index.PLUGIN_ID}.${id}`;
4
4
  exports.getTranslation = getTranslation;
@@ -77,7 +77,7 @@ const index = {
77
77
  defaultMessage: PLUGIN_ID
78
78
  },
79
79
  Component: async () => {
80
- const { App } = await Promise.resolve().then(() => require("./App-CsarVpQf.js"));
80
+ const { App } = await Promise.resolve().then(() => require("./App-CfwvfmvL.js"));
81
81
  return App;
82
82
  },
83
83
  permissions: [
@@ -98,7 +98,7 @@ const index = {
98
98
  defaultMessage: PLUGIN_ID
99
99
  },
100
100
  Component: async () => {
101
- const component = await Promise.resolve().then(() => require("./Settings-DXyz9jmT.js"));
101
+ const component = await Promise.resolve().then(() => require("./Settings-BrqTzJp-.js"));
102
102
  return component.default;
103
103
  },
104
104
  to: `plugins/${PLUGIN_ID}/settings`,
@@ -76,7 +76,7 @@ const index = {
76
76
  defaultMessage: PLUGIN_ID
77
77
  },
78
78
  Component: async () => {
79
- const { App } = await import("./App-8EiV8xw6.mjs");
79
+ const { App } = await import("./App-BQr5_g5u.mjs");
80
80
  return App;
81
81
  },
82
82
  permissions: [
@@ -97,7 +97,7 @@ const index = {
97
97
  defaultMessage: PLUGIN_ID
98
98
  },
99
99
  Component: async () => {
100
- const component = await import("./Settings-Be27Xer1.mjs");
100
+ const component = await import("./Settings-CdZnYLLK.mjs");
101
101
  return component.default;
102
102
  },
103
103
  to: `plugins/${PLUGIN_ID}/settings`,
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-DIbmEmw5.js");
2
+ const index = require("../_chunks/index-DGPqyrzz.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-DL56cMMt.mjs";
1
+ import { i } from "../_chunks/index-Dyq8dxMX.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -2,6 +2,7 @@ import { ReactNode } from 'react';
2
2
  import type { Data } from '@strapi/strapi';
3
3
  interface ImagesContextType {
4
4
  images: Data.ContentType<"plugin::upload.file">[];
5
+ downloadingId: string | null;
5
6
  onClickAction: (image: Data.ContentType<"plugin::upload.file">, action: "delete" | "download") => void;
6
7
  }
7
8
  export declare const ImagesProvider: ({ children }: {
@@ -127,6 +127,9 @@ const controller = ({ strapi }) => {
127
127
  async generateImage(ctx) {
128
128
  ctx.body = await pluginService().generateImage(ctx);
129
129
  },
130
+ async downloadFile(ctx) {
131
+ await pluginService().downloadFile(ctx);
132
+ },
130
133
  async generateVideo(ctx) {
131
134
  ctx.body = await pluginService().generateVideo(ctx);
132
135
  },
@@ -240,6 +243,15 @@ const admin = [
240
243
  auth: false
241
244
  }
242
245
  },
246
+ {
247
+ method: "POST",
248
+ path: "/download-file",
249
+ handler: "controller.downloadFile",
250
+ config: {
251
+ policies: [],
252
+ auth: false
253
+ }
254
+ },
243
255
  {
244
256
  method: "POST",
245
257
  path: "/generate-article",
@@ -2841,6 +2853,39 @@ const service = ({ strapi }) => {
2841
2853
  return await strapi.documents("plugin::upload.file").delete(sanitizedQueryParams);
2842
2854
  },
2843
2855
  // ───────────────────────────────
2856
+ // 📥 Proxy File Download (bypasses browser CORS)
2857
+ // ───────────────────────────────
2858
+ async downloadFile(ctx) {
2859
+ const { url } = ctx.request.body;
2860
+ if (!url) {
2861
+ ctx.status = 400;
2862
+ ctx.body = { error: "Se requiere el parámetro 'url'." };
2863
+ return;
2864
+ }
2865
+ let parsedUrl;
2866
+ try {
2867
+ parsedUrl = new URL(url);
2868
+ const allowedHosts = [
2869
+ "web-cms-interware.s3.us-east-1.amazonaws.com",
2870
+ "api.interware.com.mx",
2871
+ ...process.env.NODE_ENV === "development" ? ["localhost"] : []
2872
+ ];
2873
+ if (!allowedHosts.includes(parsedUrl.hostname)) {
2874
+ ctx.status = 403;
2875
+ ctx.body = { error: "URL no permitida." };
2876
+ return;
2877
+ }
2878
+ } catch {
2879
+ ctx.status = 400;
2880
+ ctx.body = { error: "URL inválida." };
2881
+ return;
2882
+ }
2883
+ const response = await axios.get(url, { responseType: "arraybuffer" });
2884
+ const base64 = Buffer.from(response.data).toString("base64");
2885
+ const contentType = response.headers["content-type"] || "application/octet-stream";
2886
+ ctx.body = { base64, contentType };
2887
+ },
2888
+ // ───────────────────────────────
2844
2889
  // 🏞️ Internal File Upload Helper
2845
2890
  // ───────────────────────────────
2846
2891
  async _uploadGeneratedFile(file, filename, mimeType, folderName = "Generated") {
@@ -108,6 +108,9 @@ const controller = ({ strapi }) => {
108
108
  async generateImage(ctx) {
109
109
  ctx.body = await pluginService().generateImage(ctx);
110
110
  },
111
+ async downloadFile(ctx) {
112
+ await pluginService().downloadFile(ctx);
113
+ },
111
114
  async generateVideo(ctx) {
112
115
  ctx.body = await pluginService().generateVideo(ctx);
113
116
  },
@@ -221,6 +224,15 @@ const admin = [
221
224
  auth: false
222
225
  }
223
226
  },
227
+ {
228
+ method: "POST",
229
+ path: "/download-file",
230
+ handler: "controller.downloadFile",
231
+ config: {
232
+ policies: [],
233
+ auth: false
234
+ }
235
+ },
224
236
  {
225
237
  method: "POST",
226
238
  path: "/generate-article",
@@ -2822,6 +2834,39 @@ const service = ({ strapi }) => {
2822
2834
  return await strapi.documents("plugin::upload.file").delete(sanitizedQueryParams);
2823
2835
  },
2824
2836
  // ───────────────────────────────
2837
+ // 📥 Proxy File Download (bypasses browser CORS)
2838
+ // ───────────────────────────────
2839
+ async downloadFile(ctx) {
2840
+ const { url } = ctx.request.body;
2841
+ if (!url) {
2842
+ ctx.status = 400;
2843
+ ctx.body = { error: "Se requiere el parámetro 'url'." };
2844
+ return;
2845
+ }
2846
+ let parsedUrl;
2847
+ try {
2848
+ parsedUrl = new URL(url);
2849
+ const allowedHosts = [
2850
+ "web-cms-interware.s3.us-east-1.amazonaws.com",
2851
+ "api.interware.com.mx",
2852
+ ...process.env.NODE_ENV === "development" ? ["localhost"] : []
2853
+ ];
2854
+ if (!allowedHosts.includes(parsedUrl.hostname)) {
2855
+ ctx.status = 403;
2856
+ ctx.body = { error: "URL no permitida." };
2857
+ return;
2858
+ }
2859
+ } catch {
2860
+ ctx.status = 400;
2861
+ ctx.body = { error: "URL inválida." };
2862
+ return;
2863
+ }
2864
+ const response = await axios.get(url, { responseType: "arraybuffer" });
2865
+ const base64 = Buffer.from(response.data).toString("base64");
2866
+ const contentType = response.headers["content-type"] || "application/octet-stream";
2867
+ ctx.body = { base64, contentType };
2868
+ },
2869
+ // ───────────────────────────────
2825
2870
  // 🏞️ Internal File Upload Helper
2826
2871
  // ───────────────────────────────
2827
2872
  async _uploadGeneratedFile(file, filename, mimeType, folderName = "Generated") {
@@ -10,6 +10,7 @@ declare const controller: ({ strapi }: {
10
10
  generateArticle(ctx: any): Promise<void>;
11
11
  generateImageForArticle(ctx: any): Promise<void>;
12
12
  generateImage(ctx: any): Promise<void>;
13
+ downloadFile(ctx: any): Promise<void>;
13
14
  generateVideo(ctx: any): Promise<void>;
14
15
  publishToLinkedIn(ctx: any): Promise<void>;
15
16
  publishToFacebook(ctx: any): Promise<void>;
@@ -10,6 +10,7 @@ declare const _default: {
10
10
  generateArticle(ctx: any): Promise<void>;
11
11
  generateImageForArticle(ctx: any): Promise<void>;
12
12
  generateImage(ctx: any): Promise<void>;
13
+ downloadFile(ctx: any): Promise<void>;
13
14
  generateVideo(ctx: any): Promise<void>;
14
15
  publishToLinkedIn(ctx: any): Promise<void>;
15
16
  publishToFacebook(ctx: any): Promise<void>;
@@ -26,6 +26,7 @@ declare const _default: {
26
26
  generateArticle(ctx: any): Promise<void>;
27
27
  generateImageForArticle(ctx: any): Promise<void>;
28
28
  generateImage(ctx: any): Promise<void>;
29
+ downloadFile(ctx: any): Promise<void>;
29
30
  generateVideo(ctx: any): Promise<void>;
30
31
  publishToLinkedIn(ctx: any): Promise<void>;
31
32
  publishToFacebook(ctx: any): Promise<void>;
@@ -170,6 +171,7 @@ declare const _default: {
170
171
  width?: number;
171
172
  }[];
172
173
  }>;
174
+ downloadFile(ctx: any): Promise<void>;
173
175
  _uploadGeneratedFile(file: string | Buffer, filename: string, mimeType: string, folderName?: string): Promise<any>;
174
176
  generateArticle(ctx: any): Promise<{
175
177
  id: import("@strapi/types/dist/data").ID;
@@ -105,6 +105,7 @@ declare const _default: {
105
105
  width?: number;
106
106
  }[];
107
107
  }>;
108
+ downloadFile(ctx: any): Promise<void>;
108
109
  _uploadGeneratedFile(file: string | Buffer, filename: string, mimeType: string, folderName?: string): Promise<any>;
109
110
  generateArticle(ctx: any): Promise<{
110
111
  id: import("@strapi/types/dist/data").ID;
@@ -106,6 +106,7 @@ declare const service: ({ strapi }: {
106
106
  width?: number;
107
107
  }[];
108
108
  }>;
109
+ downloadFile(ctx: any): Promise<void>;
109
110
  _uploadGeneratedFile(file: Buffer | string, filename: string, mimeType: string, folderName?: string): Promise<any>;
110
111
  generateArticle(ctx: any): Promise<{
111
112
  id: import("@strapi/types/dist/data").ID;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.2",
2
+ "version": "0.4.4",
3
3
  "keywords": [],
4
4
  "type": "commonjs",
5
5
  "publishConfig": {