@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.
- package/lib/commands/publish.d.ts +1 -1
- package/lib/commands/publish.js +36 -15
- package/lib/commands/serve.js +100 -10
- package/lib/utils/api.d.ts +1 -0
- package/lib/utils/api.js +10 -3
- package/package.json +1 -1
- package/src/commands/publish.ts +53 -24
- package/src/commands/serve.ts +135 -21
- package/src/utils/api.ts +13 -4
|
@@ -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: {
|
package/lib/commands/publish.js
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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) => {
|
package/lib/commands/serve.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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({
|
|
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);
|
package/lib/utils/api.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
4
|
+
"version": "5.0.335",
|
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
|
6
6
|
"contributors": [
|
|
7
7
|
{
|
package/src/commands/publish.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
224
|
+
if (!asset) {
|
|
225
|
+
Console.debug("Could not create/update asset for lang", lang)
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
203
228
|
|
|
204
|
-
|
|
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
|
|
package/src/commands/serve.ts
CHANGED
|
@@ -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, {
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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 }
|