@interware/content-generator 0.4.3 → 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-PnmqwUfI.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-B0tfTODk.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 () => {
@@ -1068,13 +1069,20 @@ const ImagesProvider = ({ children }) => {
1068
1069
  }
1069
1070
  }, [openDialog2, setOpen]);
1070
1071
  const downloadImage = async (url, filename) => {
1072
+ if (!url) return;
1073
+ const absoluteUrl = url.startsWith("/uploads") ? `${window.location.origin}${url}` : url;
1071
1074
  try {
1072
- const response = await fetch(url, {
1073
- method: "GET",
1074
- mode: "cors"
1075
- });
1076
- if (!response.ok) throw new Error("Network response was not ok");
1077
- const blob = await response.blob();
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 });
1078
1086
  const blobUrl = window.URL.createObjectURL(blob);
1079
1087
  const link = document.createElement("a");
1080
1088
  link.href = blobUrl;
@@ -1084,12 +1092,11 @@ const ImagesProvider = ({ children }) => {
1084
1092
  document.body.removeChild(link);
1085
1093
  window.URL.revokeObjectURL(blobUrl);
1086
1094
  } catch (error) {
1087
- console.error("Error downloading image:", error);
1095
+ console.error("Download error:", error);
1088
1096
  const link = document.createElement("a");
1089
- link.href = url;
1097
+ link.href = absoluteUrl;
1090
1098
  link.target = "_blank";
1091
1099
  link.rel = "noopener noreferrer";
1092
- link.download = filename;
1093
1100
  document.body.appendChild(link);
1094
1101
  link.click();
1095
1102
  document.body.removeChild(link);
@@ -1099,25 +1106,30 @@ const ImagesProvider = ({ children }) => {
1099
1106
  const image = currentImageRef.current;
1100
1107
  if (!image) return;
1101
1108
  setOpen(false);
1102
- const keys = Array.isArray(selectedKeys) ? selectedKeys : Array.from(selectedKeys);
1103
- const extension = image.ext?.replace(".", "") ?? "jpg";
1104
- const formats = image.formats;
1105
- const items = keys.map((key) => {
1106
- 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;
1107
1123
  return {
1108
- url: image.url,
1109
- filename: `image-principal-${image.documentId}.${extension}`
1124
+ url: formatImage.url,
1125
+ filename: `image-${key}-${image.documentId}.${extension}`
1110
1126
  };
1127
+ }).filter(Boolean);
1128
+ for (const item of items) {
1129
+ await downloadImage(item.url, item.filename);
1111
1130
  }
1112
- const formatImage = formats?.[key];
1113
- if (!formatImage) return null;
1114
- return {
1115
- url: formatImage.url,
1116
- filename: `image-${key}-${image.documentId}.${extension}`
1117
- };
1118
- }).filter(Boolean);
1119
- for (const item of items) {
1120
- await downloadImage(item.url, item.filename);
1131
+ } finally {
1132
+ setDownloadingId(null);
1121
1133
  }
1122
1134
  };
1123
1135
  useEffect(() => {
@@ -1146,7 +1158,7 @@ const ImagesProvider = ({ children }) => {
1146
1158
  ] })
1147
1159
  }
1148
1160
  ),
1149
- /* @__PURE__ */ jsx(ImagesContext.Provider, { value: { images, onClickAction }, children })
1161
+ /* @__PURE__ */ jsx(ImagesContext.Provider, { value: { images, downloadingId, onClickAction }, children })
1150
1162
  ] });
1151
1163
  };
1152
1164
  const useImages = () => {
@@ -1157,7 +1169,8 @@ const useImages = () => {
1157
1169
  return context;
1158
1170
  };
1159
1171
  const Image = ({ image }) => {
1160
- const { onClickAction } = useImages();
1172
+ const { onClickAction, downloadingId } = useImages();
1173
+ const isDownloading = downloadingId === image.documentId;
1161
1174
  return /* @__PURE__ */ jsxs(Card, { id: image.id, children: [
1162
1175
  /* @__PURE__ */ jsxs(CardHeader, { children: [
1163
1176
  /* @__PURE__ */ jsxs(CardAction, { position: "end", children: [
@@ -1165,10 +1178,11 @@ const Image = ({ image }) => {
1165
1178
  IconButton,
1166
1179
  {
1167
1180
  label: "Descargar",
1181
+ disabled: isDownloading,
1168
1182
  onClick: () => {
1169
1183
  onClickAction(image, "download");
1170
1184
  },
1171
- children: /* @__PURE__ */ jsx(Download, {})
1185
+ children: isDownloading ? /* @__PURE__ */ jsx(Loader, { small: true }) : /* @__PURE__ */ jsx(Download, {})
1172
1186
  }
1173
1187
  ),
1174
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-CTnsB_jp.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-DwDV99gW.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 () => {
@@ -1088,13 +1089,20 @@ const ImagesProvider = ({ children }) => {
1088
1089
  }
1089
1090
  }, [openDialog, setOpen]);
1090
1091
  const downloadImage = async (url, filename) => {
1092
+ if (!url) return;
1093
+ const absoluteUrl = url.startsWith("/uploads") ? `${window.location.origin}${url}` : url;
1091
1094
  try {
1092
- const response = await fetch(url, {
1093
- method: "GET",
1094
- mode: "cors"
1095
- });
1096
- if (!response.ok) throw new Error("Network response was not ok");
1097
- const blob = await response.blob();
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 });
1098
1106
  const blobUrl = window.URL.createObjectURL(blob);
1099
1107
  const link = document.createElement("a");
1100
1108
  link.href = blobUrl;
@@ -1104,12 +1112,11 @@ const ImagesProvider = ({ children }) => {
1104
1112
  document.body.removeChild(link);
1105
1113
  window.URL.revokeObjectURL(blobUrl);
1106
1114
  } catch (error) {
1107
- console.error("Error downloading image:", error);
1115
+ console.error("Download error:", error);
1108
1116
  const link = document.createElement("a");
1109
- link.href = url;
1117
+ link.href = absoluteUrl;
1110
1118
  link.target = "_blank";
1111
1119
  link.rel = "noopener noreferrer";
1112
- link.download = filename;
1113
1120
  document.body.appendChild(link);
1114
1121
  link.click();
1115
1122
  document.body.removeChild(link);
@@ -1119,25 +1126,30 @@ const ImagesProvider = ({ children }) => {
1119
1126
  const image = currentImageRef.current;
1120
1127
  if (!image) return;
1121
1128
  setOpen(false);
1122
- const keys = Array.isArray(selectedKeys) ? selectedKeys : Array.from(selectedKeys);
1123
- const extension = image.ext?.replace(".", "") ?? "jpg";
1124
- const formats = image.formats;
1125
- const items = keys.map((key) => {
1126
- 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;
1127
1143
  return {
1128
- url: image.url,
1129
- filename: `image-principal-${image.documentId}.${extension}`
1144
+ url: formatImage.url,
1145
+ filename: `image-${key}-${image.documentId}.${extension}`
1130
1146
  };
1147
+ }).filter(Boolean);
1148
+ for (const item of items) {
1149
+ await downloadImage(item.url, item.filename);
1131
1150
  }
1132
- const formatImage = formats?.[key];
1133
- if (!formatImage) return null;
1134
- return {
1135
- url: formatImage.url,
1136
- filename: `image-${key}-${image.documentId}.${extension}`
1137
- };
1138
- }).filter(Boolean);
1139
- for (const item of items) {
1140
- await downloadImage(item.url, item.filename);
1151
+ } finally {
1152
+ setDownloadingId(null);
1141
1153
  }
1142
1154
  };
1143
1155
  react.useEffect(() => {
@@ -1166,7 +1178,7 @@ const ImagesProvider = ({ children }) => {
1166
1178
  ] })
1167
1179
  }
1168
1180
  ),
1169
- /* @__PURE__ */ jsxRuntime.jsx(ImagesContext.Provider, { value: { images, onClickAction }, children })
1181
+ /* @__PURE__ */ jsxRuntime.jsx(ImagesContext.Provider, { value: { images, downloadingId, onClickAction }, children })
1170
1182
  ] });
1171
1183
  };
1172
1184
  const useImages = () => {
@@ -1177,7 +1189,8 @@ const useImages = () => {
1177
1189
  return context;
1178
1190
  };
1179
1191
  const Image = ({ image }) => {
1180
- const { onClickAction } = useImages();
1192
+ const { onClickAction, downloadingId } = useImages();
1193
+ const isDownloading = downloadingId === image.documentId;
1181
1194
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Card, { id: image.id, children: [
1182
1195
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.CardHeader, { children: [
1183
1196
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.CardAction, { position: "end", children: [
@@ -1185,10 +1198,11 @@ const Image = ({ image }) => {
1185
1198
  designSystem.IconButton,
1186
1199
  {
1187
1200
  label: "Descargar",
1201
+ disabled: isDownloading,
1188
1202
  onClick: () => {
1189
1203
  onClickAction(image, "download");
1190
1204
  },
1191
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {})
1205
+ children: isDownloading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true }) : /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {})
1192
1206
  }
1193
1207
  ),
1194
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-CTnsB_jp.js");
11
- const getTranslation = require("./getTranslation-DwDV99gW.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-PnmqwUfI.mjs";
9
- import { g as getTranslation } from "./getTranslation-B0tfTODk.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-PnmqwUfI.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-CTnsB_jp.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-DmCOiKrV.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-APrsjya3.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-oubMDdAZ.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-BevnW4cE.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-CTnsB_jp.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-PnmqwUfI.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.3",
2
+ "version": "0.4.4",
3
3
  "keywords": [],
4
4
  "type": "commonjs",
5
5
  "publishConfig": {