@learnpack/learnpack 5.0.333 → 5.0.335

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.
@@ -2,7 +2,7 @@ import SessionCommand from "../utils/SessionCommand";
2
2
  export declare const handleAssetCreation: (sessionPayload: {
3
3
  token: string;
4
4
  rigobotToken: string;
5
- }, learnJson: any, selectedLang: string, learnpackDeployUrl: string, b64IndexReadme: string, all_translations?: string[]) => Promise<any>;
5
+ }, learnJson: any, selectedLang: string, learnpackDeployUrl: string, b64IndexReadme: string, academyId: number | undefined, all_translations?: string[]) => Promise<any>;
6
6
  declare class BuildCommand extends SessionCommand {
7
7
  static description: string;
8
8
  static flags: {
@@ -45,7 +45,7 @@ const getLocalizedValue = (translations, lang, fallbackLangs = ["en", "us"]) =>
45
45
  const first = firstKey ? translations[firstKey] : "";
46
46
  return typeof first === "string" ? first : "";
47
47
  };
48
- const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme, all_translations = []) => {
48
+ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme, academyId, all_translations = []) => {
49
49
  const category = "uncategorized";
50
50
  try {
51
51
  const user = await api_1.default.validateToken(sessionPayload.token);
@@ -56,20 +56,31 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
56
56
  }
57
57
  let slug = (0, creatorUtilities_1.slugify)(assetTitle).slice(0, 47);
58
58
  slug = `${slug}-${selectedLang}`;
59
- const { exists } = await api_1.default.doesAssetExists(sessionPayload.token, slug);
60
- const technologies = Array.isArray(learnJson === null || learnJson === void 0 ? void 0 : learnJson.technologies) ?
61
- learnJson.technologies :
62
- [];
59
+ const { exists, academyId: existingAcademyId } = await api_1.default.doesAssetExists(sessionPayload.token, slug);
60
+ // Compare academy IDs if asset exists and academyId is provided
61
+ if (exists &&
62
+ existingAcademyId !== undefined &&
63
+ academyId !== undefined &&
64
+ existingAcademyId !== academyId) {
65
+ console_1.default.warning(`Asset exists in academy ${existingAcademyId}, but attempting to publish to academy ${academyId}. ` +
66
+ `The asset will be updated in its current academy (${existingAcademyId}).`);
67
+ }
68
+ // const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
69
+ // learnJson.technologies :
70
+ // []
63
71
  if (!exists) {
64
72
  console_1.default.info("Asset does not exist in this academy, creating it");
65
- const asset = await api_1.default.createAsset(sessionPayload.token, {
73
+ const assetPayload = {
66
74
  slug: slug,
67
75
  title: assetTitle,
68
76
  lang: selectedLang,
69
77
  graded: true,
70
78
  description: assetDescription,
71
79
  learnpack_deploy_url: learnpackDeployUrl,
72
- technologies: technologies.map((tech) => String(tech).toLowerCase().replace(/\s+/g, "-")),
80
+ // technologies: technologies.map((tech: unknown) =>
81
+ // String(tech).toLowerCase().replace(/\s+/g, "-")
82
+ // ),
83
+ technologies: [],
73
84
  url: learnpackDeployUrl,
74
85
  category: category,
75
86
  owner: user.id,
@@ -77,7 +88,11 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
77
88
  preview: learnJson.preview,
78
89
  readme_raw: b64IndexReadme,
79
90
  all_translations,
80
- });
91
+ };
92
+ if (academyId !== undefined) {
93
+ assetPayload.academy_id = academyId;
94
+ }
95
+ const asset = await api_1.default.createAsset(sessionPayload.token, assetPayload);
81
96
  try {
82
97
  await api_1.default.updateRigoPackage(sessionPayload.rigobotToken.trim(), learnJson.slug, {
83
98
  asset_id: asset.id,
@@ -111,7 +126,7 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
111
126
  }
112
127
  catch (error) {
113
128
  console_1.default.error("Error updating or creating asset:", error);
114
- return null;
129
+ throw error;
115
130
  }
116
131
  };
117
132
  exports.handleAssetCreation = handleAssetCreation;
@@ -135,13 +150,19 @@ const createMultiLangAssetFromDisk = async (sessionPayload, learnJson, deployUrl
135
150
  indexReadmeString = "";
136
151
  }
137
152
  const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
138
- // eslint-disable-next-line no-await-in-loop
139
- const asset = await (0, exports.handleAssetCreation)(sessionPayload, learnJson, lang, deployUrl, b64IndexReadme, all_translations);
140
- if (!asset) {
141
- console_1.default.debug("Could not create/update asset for lang", lang);
142
- continue;
153
+ try {
154
+ // eslint-disable-next-line no-await-in-loop
155
+ const asset = await (0, exports.handleAssetCreation)(sessionPayload, learnJson, lang, deployUrl, b64IndexReadme, undefined, all_translations);
156
+ if (!asset) {
157
+ console_1.default.debug("Could not create/update asset for lang", lang);
158
+ continue;
159
+ }
160
+ all_translations.push(asset.slug);
161
+ }
162
+ catch (error) {
163
+ console_1.default.error("Error creating asset for language", lang, error);
164
+ // Continue with other languages
143
165
  }
144
- all_translations.push(asset.slug);
145
166
  }
146
167
  };
147
168
  const runAudit = (strict) => {
@@ -155,10 +155,27 @@ const cleanFormState = (formState) => {
155
155
  const cleanFormStateForSyllabus = (formState) => {
156
156
  return Object.assign(Object.assign({}, formState), { description: formState.description, technologies: formState.technologies, contentIndex: formState.contentIndex, purposse: undefined, duration: undefined, hasContentIndex: undefined, variables: undefined, currentStep: undefined, language: undefined, isCompleted: undefined });
157
157
  };
158
- const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, courseJson, deployUrl) => {
158
+ const getLocalizedValue = (translations, lang, fallbackLangs = ["en", "us"]) => {
159
+ if (!translations || typeof translations !== "object")
160
+ return "";
161
+ const direct = translations[lang];
162
+ if (typeof direct === "string" && direct.trim().length > 0)
163
+ return direct;
164
+ for (const fb of fallbackLangs) {
165
+ const v = translations[fb];
166
+ if (typeof v === "string" && v.trim().length > 0)
167
+ return v;
168
+ }
169
+ const firstKey = Object.keys(translations)[0];
170
+ const first = firstKey ? translations[firstKey] : "";
171
+ return typeof first === "string" ? first : "";
172
+ };
173
+ const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, courseJson, deployUrl, academyId) => {
174
+ var _a;
159
175
  const availableLangs = Object.keys(courseJson.title);
160
176
  console.log("AVAILABLE LANGUAGES to upload asset", availableLangs);
161
177
  const all_translations = [];
178
+ const errors = [];
162
179
  for (const lang of availableLangs) {
163
180
  // eslint-disable-next-line no-await-in-loop
164
181
  const indexReadme = await bucket.file(`courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
@@ -174,14 +191,28 @@ const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, cour
174
191
  indexReadmeString = "";
175
192
  }
176
193
  const b64IndexReadme = buffer_1.Buffer.from(indexReadmeString).toString("base64");
177
- // eslint-disable-next-line no-await-in-loop
178
- const asset = await (0, publish_1.handleAssetCreation)({ token: bcToken, rigobotToken: rigoToken.trim() }, courseJson, lang, deployUrl, b64IndexReadme, all_translations);
179
- if (!asset) {
180
- console.log("No se pudo crear el asset, saltando idioma", lang);
181
- continue;
194
+ try {
195
+ // eslint-disable-next-line no-await-in-loop
196
+ const asset = await (0, publish_1.handleAssetCreation)({ token: bcToken, rigobotToken: rigoToken.trim() }, courseJson, lang, deployUrl, b64IndexReadme, academyId, all_translations);
197
+ if (!asset) {
198
+ errors.push({
199
+ lang,
200
+ error: { detail: "Failed to create asset", status_code: 500 },
201
+ });
202
+ console.log("No se pudo crear el asset, saltando idioma", lang);
203
+ continue;
204
+ }
205
+ all_translations.push(asset.slug);
206
+ }
207
+ catch (error) {
208
+ const errorData = error && typeof error === "object" && "response" in error ?
209
+ ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error :
210
+ error;
211
+ errors.push({ lang, error: errorData });
212
+ console.error(`Error creating asset for language ${lang}:`, error);
182
213
  }
183
- all_translations.push(asset.slug);
184
214
  }
215
+ return { errors };
185
216
  };
186
217
  const lessonCleaner = (lesson) => {
187
218
  return Object.assign(Object.assign({}, lesson), { duration: undefined, generated: undefined, status: undefined, translations: undefined, uid: undefined, initialContent: undefined, locked: undefined });
@@ -2907,12 +2938,68 @@ class ServeCommand extends SessionCommand_1.default {
2907
2938
  return res.status(500).json({ error: error.message });
2908
2939
  }
2909
2940
  });
2941
+ app.get("/actions/academies", async (req, res) => {
2942
+ try {
2943
+ const bcToken = req.header("x-breathecode-token");
2944
+ if (!bcToken) {
2945
+ return res.status(400).json({
2946
+ error: "Authentication failed, missing breathecode token",
2947
+ });
2948
+ }
2949
+ const academies = await (0, api_1.listUserAcademies)(bcToken);
2950
+ return res.json(academies);
2951
+ }
2952
+ catch (error) {
2953
+ console.error("Error fetching academies:", error);
2954
+ return res.status(500).json({ error: error.message });
2955
+ }
2956
+ });
2957
+ app.get("/actions/package-academy/:slug", async (req, res) => {
2958
+ try {
2959
+ const { slug } = req.params;
2960
+ const bcToken = req.header("x-breathecode-token");
2961
+ if (!bcToken) {
2962
+ return res.status(400).json({
2963
+ error: "Authentication failed, missing breathecode token",
2964
+ });
2965
+ }
2966
+ const configFile = await bucket.file(`courses/${slug}/.learn/config.json`);
2967
+ const [configContent] = await configFile.download();
2968
+ const configJson = JSON.parse(configContent.toString());
2969
+ const { config } = configJson;
2970
+ const availableLangs = Object.keys(config.title || {});
2971
+ let academyId = null;
2972
+ let isPublished = false;
2973
+ for (const lang of availableLangs) {
2974
+ const assetTitle = getLocalizedValue(config.title, lang);
2975
+ if (!assetTitle)
2976
+ continue;
2977
+ let assetSlug = (0, creatorUtilities_2.slugify)(assetTitle).slice(0, 47);
2978
+ assetSlug = `${assetSlug}-${lang}`;
2979
+ const { exists, academyId: existingAcademyId } =
2980
+ // eslint-disable-next-line no-await-in-loop
2981
+ await (0, api_1.doesAssetExists)(bcToken, assetSlug);
2982
+ if (exists) {
2983
+ isPublished = true;
2984
+ if (existingAcademyId !== undefined) {
2985
+ academyId = existingAcademyId;
2986
+ break;
2987
+ }
2988
+ }
2989
+ }
2990
+ return res.json({ academyId, isPublished });
2991
+ }
2992
+ catch (error) {
2993
+ console.error("Error fetching package academy:", error);
2994
+ return res.status(500).json({ error: error.message });
2995
+ }
2996
+ });
2910
2997
  app.post("/actions/publish/:slug", async (req, res) => {
2911
2998
  try {
2912
2999
  const { slug } = req.params;
2913
3000
  const rigoToken = req.header("x-rigo-token");
2914
3001
  const bcToken = req.header("x-breathecode-token");
2915
- // const { academyId, categoryId } = req.body
3002
+ const { academyId } = req.body;
2916
3003
  if (!rigoToken || !bcToken) {
2917
3004
  return res
2918
3005
  .status(400)
@@ -2998,10 +3085,13 @@ class ServeCommand extends SessionCommand_1.default {
2998
3085
  const rigoRes = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/learnpack/upload`, form, {
2999
3086
  headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: "Token " + rigoToken.trim() }),
3000
3087
  });
3001
- await createMultiLangAsset(bucket, rigoToken, bcToken, slug, fullConfig.config, rigoRes.data.url);
3088
+ const assetResults = await createMultiLangAsset(bucket, rigoToken, bcToken, slug, fullConfig.config, rigoRes.data.url, academyId);
3002
3089
  rimraf.sync(tmpRoot);
3003
3090
  console.log("RigoRes", rigoRes.data);
3004
- return res.json({ url: rigoRes.data.url });
3091
+ return res.json({
3092
+ url: rigoRes.data.url,
3093
+ errors: assetResults.errors,
3094
+ });
3005
3095
  });
3006
3096
  archive.on("error", err => {
3007
3097
  console.error("ZIP Error:", err);
@@ -26,6 +26,7 @@ type TAssetMissing = {
26
26
  preview: string;
27
27
  readme_raw: string;
28
28
  all_translations: string[];
29
+ academy_id?: number;
29
30
  };
30
31
  export declare const createAsset: (token: string, asset: TAssetMissing) => Promise<any>;
31
32
  export declare const doesAssetExists: (token: string, assetSlug: string) => Promise<{
package/lib/utils/api.js CHANGED
@@ -358,10 +358,15 @@ const createAsset = async (token, asset) => {
358
358
  readme_raw: asset.readme_raw,
359
359
  all_translations: asset.all_translations,
360
360
  };
361
- const url = `https://breathecode.herokuapp.com/v1/registry/asset/me`;
361
+ let url = `https://breathecode.herokuapp.com/v1/registry/asset/me`;
362
362
  const headers = {
363
363
  Authorization: `Token ${token}`,
364
364
  };
365
+ // Use academy-specific endpoint if academy_id is provided
366
+ if (asset.academy_id !== undefined) {
367
+ url = `https://breathecode.herokuapp.com/v1/registry/academy/asset`;
368
+ headers.Academy = String(asset.academy_id);
369
+ }
365
370
  try {
366
371
  const response = await axios_1.default.post(url, body, { headers });
367
372
  return response.data;
@@ -373,6 +378,7 @@ const createAsset = async (token, asset) => {
373
378
  };
374
379
  exports.createAsset = createAsset;
375
380
  const doesAssetExists = async (token, assetSlug) => {
381
+ var _a, _b, _c;
376
382
  const url = `https://breathecode.herokuapp.com/v1/registry/asset/${assetSlug}`;
377
383
  const headers = {
378
384
  Authorization: `Token ${token}`,
@@ -380,11 +386,12 @@ const doesAssetExists = async (token, assetSlug) => {
380
386
  try {
381
387
  const response = await axios_1.default.get(url, { headers });
382
388
  if (response.status === 200) {
383
- return { exists: true };
389
+ const academyId = ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.academy) === null || _b === void 0 ? void 0 : _b.id) || ((_c = response.data) === null || _c === void 0 ? void 0 : _c.academy_id);
390
+ return { exists: true, academyId };
384
391
  }
385
392
  return { exists: false };
386
393
  }
387
- catch (_a) {
394
+ catch (_d) {
388
395
  // console.error("Failed to get asset:", error)
389
396
  return { exists: false };
390
397
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.333",
4
+ "version": "5.0.335",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -60,6 +60,7 @@ export const handleAssetCreation = async (
60
60
  selectedLang: string,
61
61
  learnpackDeployUrl: string,
62
62
  b64IndexReadme: string,
63
+ academyId: number | undefined,
63
64
  all_translations: string[] = []
64
65
  ) => {
65
66
  const category = "uncategorized"
@@ -82,24 +83,41 @@ export const handleAssetCreation = async (
82
83
  let slug = slugify(assetTitle).slice(0, 47)
83
84
  slug = `${slug}-${selectedLang}`
84
85
 
85
- const { exists } = await api.doesAssetExists(sessionPayload.token, slug)
86
+ const { exists, academyId: existingAcademyId } = await api.doesAssetExists(
87
+ sessionPayload.token,
88
+ slug
89
+ )
90
+
91
+ // Compare academy IDs if asset exists and academyId is provided
92
+ if (
93
+ exists &&
94
+ existingAcademyId !== undefined &&
95
+ academyId !== undefined &&
96
+ existingAcademyId !== academyId
97
+ ) {
98
+ Console.warning(
99
+ `Asset exists in academy ${existingAcademyId}, but attempting to publish to academy ${academyId}. ` +
100
+ `The asset will be updated in its current academy (${existingAcademyId}).`
101
+ )
102
+ }
86
103
 
87
- const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
88
- learnJson.technologies :
89
- []
104
+ // const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
105
+ // learnJson.technologies :
106
+ // []
90
107
 
91
108
  if (!exists) {
92
109
  Console.info("Asset does not exist in this academy, creating it")
93
- const asset = await api.createAsset(sessionPayload.token, {
110
+ const assetPayload: any = {
94
111
  slug: slug,
95
112
  title: assetTitle,
96
113
  lang: selectedLang,
97
114
  graded: true,
98
115
  description: assetDescription,
99
116
  learnpack_deploy_url: learnpackDeployUrl,
100
- technologies: technologies.map((tech: unknown) =>
101
- String(tech).toLowerCase().replace(/\s+/g, "-")
102
- ),
117
+ // technologies: technologies.map((tech: unknown) =>
118
+ // String(tech).toLowerCase().replace(/\s+/g, "-")
119
+ // ),
120
+ technologies: [],
103
121
  url: learnpackDeployUrl,
104
122
  category: category,
105
123
  owner: user.id,
@@ -107,7 +125,12 @@ export const handleAssetCreation = async (
107
125
  preview: learnJson.preview,
108
126
  readme_raw: b64IndexReadme,
109
127
  all_translations,
110
- })
128
+ }
129
+ if (academyId !== undefined) {
130
+ assetPayload.academy_id = academyId
131
+ }
132
+
133
+ const asset = await api.createAsset(sessionPayload.token, assetPayload)
111
134
  try {
112
135
  await api.updateRigoPackage(
113
136
  sessionPayload.rigobotToken.trim(),
@@ -149,7 +172,7 @@ export const handleAssetCreation = async (
149
172
  return asset
150
173
  } catch (error) {
151
174
  Console.error("Error updating or creating asset:", error)
152
- return null
175
+ throw error
153
176
  }
154
177
  }
155
178
 
@@ -186,22 +209,28 @@ const createMultiLangAssetFromDisk = async (
186
209
 
187
210
  const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
188
211
 
189
- // eslint-disable-next-line no-await-in-loop
190
- const asset = await handleAssetCreation(
191
- sessionPayload,
192
- learnJson,
193
- lang,
194
- deployUrl,
195
- b64IndexReadme,
196
- all_translations
197
- )
212
+ try {
213
+ // eslint-disable-next-line no-await-in-loop
214
+ const asset = await handleAssetCreation(
215
+ sessionPayload,
216
+ learnJson,
217
+ lang,
218
+ deployUrl,
219
+ b64IndexReadme,
220
+ undefined,
221
+ all_translations
222
+ )
198
223
 
199
- if (!asset) {
200
- Console.debug("Could not create/update asset for lang", lang)
201
- continue
202
- }
224
+ if (!asset) {
225
+ Console.debug("Could not create/update asset for lang", lang)
226
+ continue
227
+ }
203
228
 
204
- all_translations.push(asset.slug)
229
+ all_translations.push(asset.slug)
230
+ } catch (error) {
231
+ Console.error("Error creating asset for language", lang, error)
232
+ // Continue with other languages
233
+ }
205
234
  }
206
235
  }
207
236
 
@@ -49,7 +49,12 @@ import {
49
49
  // import { handleAssetCreation } from "./publish"
50
50
  import axios from "axios"
51
51
  import * as FormData from "form-data"
52
- import api, { RIGOBOT_HOST, RIGOBOT_REALTIME_HOST } from "../utils/api"
52
+ import api, {
53
+ RIGOBOT_HOST,
54
+ RIGOBOT_REALTIME_HOST,
55
+ listUserAcademies,
56
+ doesAssetExists,
57
+ } from "../utils/api"
53
58
  import {
54
59
  createUploadMiddleware,
55
60
  minutesToISO8601Duration,
@@ -273,18 +278,40 @@ const cleanFormStateForSyllabus = (formState: FormState) => {
273
278
  }
274
279
  }
275
280
 
281
+ const getLocalizedValue = (
282
+ translations: Record<string, any> | undefined,
283
+ lang: string,
284
+ fallbackLangs: string[] = ["en", "us"]
285
+ ): string => {
286
+ if (!translations || typeof translations !== "object") return ""
287
+
288
+ const direct = translations[lang]
289
+ if (typeof direct === "string" && direct.trim().length > 0) return direct
290
+
291
+ for (const fb of fallbackLangs) {
292
+ const v = translations[fb]
293
+ if (typeof v === "string" && v.trim().length > 0) return v
294
+ }
295
+
296
+ const firstKey = Object.keys(translations)[0]
297
+ const first = firstKey ? translations[firstKey] : ""
298
+ return typeof first === "string" ? first : ""
299
+ }
300
+
276
301
  const createMultiLangAsset = async (
277
302
  bucket: Bucket,
278
303
  rigoToken: string,
279
304
  bcToken: string,
280
305
  courseSlug: string,
281
306
  courseJson: any,
282
- deployUrl: string
283
- ) => {
307
+ deployUrl: string,
308
+ academyId?: number
309
+ ): Promise<{ errors: Array<{ lang: string; error: any }> }> => {
284
310
  const availableLangs = Object.keys(courseJson.title)
285
311
  console.log("AVAILABLE LANGUAGES to upload asset", availableLangs)
286
312
 
287
313
  const all_translations: string[] = []
314
+ const errors: Array<{ lang: string; error: any }> = []
288
315
 
289
316
  for (const lang of availableLangs) {
290
317
  // eslint-disable-next-line no-await-in-loop
@@ -304,23 +331,39 @@ const createMultiLangAsset = async (
304
331
 
305
332
  const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
306
333
 
307
- // eslint-disable-next-line no-await-in-loop
308
- const asset = await handleAssetCreation(
309
- { token: bcToken, rigobotToken: rigoToken.trim() },
310
- courseJson,
311
- lang,
312
- deployUrl,
313
- b64IndexReadme,
314
- all_translations
315
- )
334
+ try {
335
+ // eslint-disable-next-line no-await-in-loop
336
+ const asset = await handleAssetCreation(
337
+ { token: bcToken, rigobotToken: rigoToken.trim() },
338
+ courseJson,
339
+ lang,
340
+ deployUrl,
341
+ b64IndexReadme,
342
+ academyId,
343
+ all_translations
344
+ )
316
345
 
317
- if (!asset) {
318
- console.log("No se pudo crear el asset, saltando idioma", lang)
319
- continue
320
- }
346
+ if (!asset) {
347
+ errors.push({
348
+ lang,
349
+ error: { detail: "Failed to create asset", status_code: 500 },
350
+ })
351
+ console.log("No se pudo crear el asset, saltando idioma", lang)
352
+ continue
353
+ }
321
354
 
322
- all_translations.push(asset.slug)
355
+ all_translations.push(asset.slug)
356
+ } catch (error) {
357
+ const errorData =
358
+ error && typeof error === "object" && "response" in error ?
359
+ (error as any).response?.data || error :
360
+ error
361
+ errors.push({ lang, error: errorData })
362
+ console.error(`Error creating asset for language ${lang}:`, error)
363
+ }
323
364
  }
365
+
366
+ return { errors }
324
367
  }
325
368
 
326
369
  const lessonCleaner = (lesson: Lesson) => {
@@ -4233,12 +4276,79 @@ class ServeCommand extends SessionCommand {
4233
4276
  }
4234
4277
  )
4235
4278
 
4279
+ app.get("/actions/academies", async (req, res) => {
4280
+ try {
4281
+ const bcToken = req.header("x-breathecode-token")
4282
+
4283
+ if (!bcToken) {
4284
+ return res.status(400).json({
4285
+ error: "Authentication failed, missing breathecode token",
4286
+ })
4287
+ }
4288
+
4289
+ const academies = await listUserAcademies(bcToken)
4290
+ return res.json(academies)
4291
+ } catch (error) {
4292
+ console.error("Error fetching academies:", error)
4293
+ return res.status(500).json({ error: (error as Error).message })
4294
+ }
4295
+ })
4296
+
4297
+ app.get("/actions/package-academy/:slug", async (req, res) => {
4298
+ try {
4299
+ const { slug } = req.params
4300
+ const bcToken = req.header("x-breathecode-token")
4301
+
4302
+ if (!bcToken) {
4303
+ return res.status(400).json({
4304
+ error: "Authentication failed, missing breathecode token",
4305
+ })
4306
+ }
4307
+
4308
+ const configFile = await bucket.file(
4309
+ `courses/${slug}/.learn/config.json`
4310
+ )
4311
+ const [configContent] = await configFile.download()
4312
+ const configJson = JSON.parse(configContent.toString())
4313
+ const { config } = configJson
4314
+
4315
+ const availableLangs = Object.keys(config.title || {})
4316
+ let academyId: number | null = null
4317
+ let isPublished = false
4318
+
4319
+ for (const lang of availableLangs) {
4320
+ const assetTitle = getLocalizedValue(config.title, lang)
4321
+ if (!assetTitle) continue
4322
+
4323
+ let assetSlug = slugify(assetTitle).slice(0, 47)
4324
+ assetSlug = `${assetSlug}-${lang}`
4325
+
4326
+ const { exists, academyId: existingAcademyId } =
4327
+ // eslint-disable-next-line no-await-in-loop
4328
+ await doesAssetExists(bcToken, assetSlug)
4329
+
4330
+ if (exists) {
4331
+ isPublished = true
4332
+ if (existingAcademyId !== undefined) {
4333
+ academyId = existingAcademyId
4334
+ break
4335
+ }
4336
+ }
4337
+ }
4338
+
4339
+ return res.json({ academyId, isPublished })
4340
+ } catch (error) {
4341
+ console.error("Error fetching package academy:", error)
4342
+ return res.status(500).json({ error: (error as Error).message })
4343
+ }
4344
+ })
4345
+
4236
4346
  app.post("/actions/publish/:slug", async (req, res) => {
4237
4347
  try {
4238
4348
  const { slug } = req.params
4239
4349
  const rigoToken = req.header("x-rigo-token")
4240
4350
  const bcToken = req.header("x-breathecode-token")
4241
- // const { academyId, categoryId } = req.body
4351
+ const { academyId } = req.body
4242
4352
 
4243
4353
  if (!rigoToken || !bcToken) {
4244
4354
  return res
@@ -4360,18 +4470,22 @@ class ServeCommand extends SessionCommand {
4360
4470
  }
4361
4471
  )
4362
4472
 
4363
- await createMultiLangAsset(
4473
+ const assetResults = await createMultiLangAsset(
4364
4474
  bucket,
4365
4475
  rigoToken,
4366
4476
  bcToken,
4367
4477
  slug,
4368
4478
  fullConfig.config,
4369
- rigoRes.data.url
4479
+ rigoRes.data.url,
4480
+ academyId
4370
4481
  )
4371
4482
 
4372
4483
  rimraf.sync(tmpRoot)
4373
4484
  console.log("RigoRes", rigoRes.data)
4374
- return res.json({ url: rigoRes.data.url })
4485
+ return res.json({
4486
+ url: rigoRes.data.url,
4487
+ errors: assetResults.errors,
4488
+ })
4375
4489
  })
4376
4490
 
4377
4491
  archive.on("error", err => {
package/src/utils/api.ts CHANGED
@@ -431,10 +431,11 @@ type TAssetMissing = {
431
431
  preview: string;
432
432
  readme_raw: string;
433
433
  all_translations: string[];
434
+ academy_id?: number;
434
435
  };
435
436
 
436
437
  export const createAsset = async (token: string, asset: TAssetMissing) => {
437
- const body = {
438
+ const body: any = {
438
439
  slug: asset.slug,
439
440
  title: asset.title,
440
441
  lang: asset.lang,
@@ -462,11 +463,18 @@ export const createAsset = async (token: string, asset: TAssetMissing) => {
462
463
  readme_raw: asset.readme_raw,
463
464
  all_translations: asset.all_translations,
464
465
  }
465
- const url = `https://breathecode.herokuapp.com/v1/registry/asset/me`
466
- const headers = {
466
+
467
+ let url = `https://breathecode.herokuapp.com/v1/registry/asset/me`
468
+ const headers: any = {
467
469
  Authorization: `Token ${token}`,
468
470
  }
469
471
 
472
+ // Use academy-specific endpoint if academy_id is provided
473
+ if (asset.academy_id !== undefined) {
474
+ url = `https://breathecode.herokuapp.com/v1/registry/academy/asset`
475
+ headers.Academy = String(asset.academy_id)
476
+ }
477
+
470
478
  try {
471
479
  const response = await axios.post(url, body, { headers })
472
480
  return response.data
@@ -489,7 +497,8 @@ export const doesAssetExists = async (
489
497
  try {
490
498
  const response = await axios.get(url, { headers })
491
499
  if (response.status === 200) {
492
- return { exists: true }
500
+ const academyId = response.data?.academy?.id || response.data?.academy_id
501
+ return { exists: true, academyId }
493
502
  }
494
503
 
495
504
  return { exists: false }