@learnpack/learnpack 5.0.344 → 5.0.347
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 +7 -1
- package/lib/commands/publish.js +71 -21
- package/lib/commands/serve.js +126 -28
- package/lib/utils/api.d.ts +25 -1
- package/lib/utils/api.js +71 -18
- package/package.json +1 -1
- package/src/commands/publish.ts +122 -30
- package/src/commands/serve.ts +146 -47
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +511 -512
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +93 -21
package/src/ui/app.tar.gz
CHANGED
|
Binary file
|
package/src/utils/api.ts
CHANGED
|
@@ -353,20 +353,15 @@ export interface TAcademy {
|
|
|
353
353
|
timezone: string;
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
const
|
|
357
|
-
"add_asset",
|
|
358
|
-
"change_asset",
|
|
359
|
-
"view_asset",
|
|
360
|
-
"delete_asset",
|
|
361
|
-
]
|
|
356
|
+
const CRUD_ASSET_CAPABILITY_URL = `${HOST}/v1/auth/user/me/capability/crud_asset`
|
|
362
357
|
|
|
363
358
|
export const listUserAcademies = async (
|
|
364
359
|
breathecodeToken: string
|
|
365
360
|
): Promise<TAcademy[]> => {
|
|
366
|
-
const
|
|
361
|
+
const meUrl = `${HOST}/v1/auth/user/me`
|
|
367
362
|
|
|
368
363
|
try {
|
|
369
|
-
const response = await axios.get(
|
|
364
|
+
const response = await axios.get(meUrl, {
|
|
370
365
|
headers: {
|
|
371
366
|
Authorization: `Token ${breathecodeToken}`,
|
|
372
367
|
},
|
|
@@ -376,27 +371,47 @@ export const listUserAcademies = async (
|
|
|
376
371
|
|
|
377
372
|
const academiesMap = new Map<number, TAcademy>()
|
|
378
373
|
|
|
374
|
+
if (!Array.isArray(data.roles)) {
|
|
375
|
+
return []
|
|
376
|
+
}
|
|
377
|
+
|
|
379
378
|
for (const role of data.roles) {
|
|
380
379
|
const academy = role.academy
|
|
380
|
+
if (!academy) continue
|
|
381
381
|
if (!academiesMap.has(academy.id)) {
|
|
382
382
|
academiesMap.set(academy.id, academy)
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
const
|
|
386
|
+
const academies = [...academiesMap.values()]
|
|
387
387
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
388
|
+
const allowed = await Promise.all(
|
|
389
|
+
academies.map(async academy => {
|
|
390
|
+
const capResponse = await axios.get(CRUD_ASSET_CAPABILITY_URL, {
|
|
391
|
+
headers: {
|
|
392
|
+
Authorization: `Token ${breathecodeToken}`,
|
|
393
|
+
Academy: academy.id,
|
|
394
|
+
},
|
|
395
|
+
validateStatus: () => true,
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
if (capResponse.status === 200) {
|
|
399
|
+
return academy
|
|
400
|
+
}
|
|
392
401
|
|
|
393
|
-
|
|
394
|
-
|
|
402
|
+
if (capResponse.status !== 403) {
|
|
403
|
+
console.warn(
|
|
404
|
+
`listUserAcademies: unexpected status ${capResponse.status} for academy ${academy.id}`
|
|
405
|
+
)
|
|
406
|
+
}
|
|
395
407
|
|
|
396
|
-
|
|
397
|
-
|
|
408
|
+
return null
|
|
409
|
+
})
|
|
410
|
+
)
|
|
398
411
|
|
|
399
|
-
|
|
412
|
+
const filtered = allowed.filter((a): a is TAcademy => a !== null)
|
|
413
|
+
filtered.sort((a, b) => a.name.localeCompare(b.name))
|
|
414
|
+
return filtered
|
|
400
415
|
} catch (error) {
|
|
401
416
|
console.error("Failed to fetch user academies:", error)
|
|
402
417
|
return []
|
|
@@ -417,6 +432,42 @@ export const validateToken = async (token: string) => {
|
|
|
417
432
|
}
|
|
418
433
|
}
|
|
419
434
|
|
|
435
|
+
/** keep in sync with ide/src/components/Creator/PublishButton.tsx AssetSyncError */
|
|
436
|
+
export type AssetSyncError =
|
|
437
|
+
| { kind: "lang_error"; lang: string; error: { detail: string } }
|
|
438
|
+
| { kind: "package_error"; error: { detail: string } };
|
|
439
|
+
|
|
440
|
+
function parseLearnpackPackageId(raw: unknown): number | null {
|
|
441
|
+
if (raw === undefined || raw === null) return null
|
|
442
|
+
const n = typeof raw === "number" ? raw : Number(raw)
|
|
443
|
+
if (!Number.isFinite(n) || !Number.isInteger(n)) return null
|
|
444
|
+
return n
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* GET Rigobot package by slug after a successful upload. Does not throw.
|
|
449
|
+
* @param rigoToken Rigobot API token (Bearer-style `Token` header value).
|
|
450
|
+
* @param courseSlug Learnpack package slug used in the Rigobot URL path.
|
|
451
|
+
* @returns Resolved numeric package id from Rigobot, or `null` if the request fails, the id is missing, or it is not a finite integer.
|
|
452
|
+
*/
|
|
453
|
+
export async function resolveLearnpackPackageId(
|
|
454
|
+
rigoToken: string,
|
|
455
|
+
courseSlug: string
|
|
456
|
+
): Promise<number | null> {
|
|
457
|
+
if (!rigoToken?.trim() || !courseSlug) return null
|
|
458
|
+
const url = `${RIGOBOT_HOST}/v1/learnpack/package/${encodeURIComponent(
|
|
459
|
+
courseSlug
|
|
460
|
+
)}/`
|
|
461
|
+
try {
|
|
462
|
+
const response = await axios.get<{ id?: unknown }>(url, {
|
|
463
|
+
headers: { Authorization: `Token ${rigoToken.trim()}` },
|
|
464
|
+
})
|
|
465
|
+
return parseLearnpackPackageId(response.data?.id)
|
|
466
|
+
} catch {
|
|
467
|
+
return null
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
420
471
|
type TAssetMissing = {
|
|
421
472
|
slug: string;
|
|
422
473
|
title: string;
|
|
@@ -433,6 +484,7 @@ type TAssetMissing = {
|
|
|
433
484
|
readme_raw: string;
|
|
434
485
|
all_translations: string[];
|
|
435
486
|
academy_id?: number;
|
|
487
|
+
learnpack_id: number;
|
|
436
488
|
};
|
|
437
489
|
|
|
438
490
|
export const createAsset = async (token: string, asset: TAssetMissing) => {
|
|
@@ -460,6 +512,7 @@ export const createAsset = async (token: string, asset: TAssetMissing) => {
|
|
|
460
512
|
intro_video_url: null,
|
|
461
513
|
translations: [asset.lang],
|
|
462
514
|
learnpack_deploy_url: asset.learnpack_deploy_url,
|
|
515
|
+
learnpack_id: asset.learnpack_id,
|
|
463
516
|
technologies: asset.technologies,
|
|
464
517
|
readme_raw: asset.readme_raw,
|
|
465
518
|
all_translations: asset.all_translations,
|
|
@@ -476,6 +529,15 @@ export const createAsset = async (token: string, asset: TAssetMissing) => {
|
|
|
476
529
|
headers.Academy = String(asset.academy_id)
|
|
477
530
|
}
|
|
478
531
|
|
|
532
|
+
console.log(
|
|
533
|
+
"[BC] POST",
|
|
534
|
+
url,
|
|
535
|
+
"| academy_id:",
|
|
536
|
+
asset.academy_id ?? "none",
|
|
537
|
+
"| slug:",
|
|
538
|
+
asset.slug
|
|
539
|
+
)
|
|
540
|
+
|
|
479
541
|
try {
|
|
480
542
|
const response = await axios.post(url, body, { headers })
|
|
481
543
|
return response.data
|
|
@@ -512,14 +574,19 @@ export const doesAssetExists = async (
|
|
|
512
574
|
const updateAsset = async (
|
|
513
575
|
token: string,
|
|
514
576
|
assetSlug: string,
|
|
515
|
-
asset: Partial<TAssetMissing>
|
|
577
|
+
asset: Partial<TAssetMissing> & { learnpack_id: number }
|
|
516
578
|
) => {
|
|
517
579
|
const url = `https://breathecode.herokuapp.com/v1/registry/asset/me/${assetSlug}`
|
|
518
580
|
const headers = {
|
|
519
581
|
Authorization: `Token ${token}`,
|
|
520
582
|
}
|
|
583
|
+
|
|
584
|
+
const body = { ...asset, learnpack_id: asset.learnpack_id }
|
|
585
|
+
|
|
586
|
+
console.log("[BC] PUT", url, "| academy_id:", asset.academy_id ?? "none")
|
|
587
|
+
|
|
521
588
|
try {
|
|
522
|
-
const response = await axios.put(url,
|
|
589
|
+
const response = await axios.put(url, body, { headers })
|
|
523
590
|
return response.data
|
|
524
591
|
} catch (error: any) {
|
|
525
592
|
console.error("Failed to update asset:", error)
|
|
@@ -593,7 +660,11 @@ type TTechnology = {
|
|
|
593
660
|
|
|
594
661
|
let technologiesCache: TTechnology[] = []
|
|
595
662
|
|
|
596
|
-
/**
|
|
663
|
+
/**
|
|
664
|
+
* Strip .env comments (e.g. "token # comment") so the token is sent without spaces.
|
|
665
|
+
* @param token - Raw token value from env, possibly with trailing comment.
|
|
666
|
+
* @returns Trimmed token with inline `#` comments removed, or empty string if missing.
|
|
667
|
+
*/
|
|
597
668
|
function sanitizeToken(token: string | undefined): string {
|
|
598
669
|
if (!token) return ""
|
|
599
670
|
const trimmed = token.trim()
|
|
@@ -677,6 +748,7 @@ export default {
|
|
|
677
748
|
createAsset,
|
|
678
749
|
doesAssetExists,
|
|
679
750
|
updateAsset,
|
|
751
|
+
resolveLearnpackPackageId,
|
|
680
752
|
getCategories,
|
|
681
753
|
updateRigoPackage,
|
|
682
754
|
createRigoPackage,
|