playcademy 0.14.19 → 0.14.20
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/README.md +20 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -2
- package/dist/db.js +14 -7
- package/dist/edge-play/src/register-routes.ts +7 -0
- package/dist/edge-play/src/routes/health.ts +18 -7
- package/dist/edge-play/src/routes/integrations/timeback/end-activity.ts +113 -49
- package/dist/edge-play/src/types.ts +2 -0
- package/dist/index.d.ts +79 -19
- package/dist/index.js +7553 -7573
- package/dist/templates/api/sample-route-with-db.ts.template +1 -1
- package/dist/templates/api/sample-route.ts.template +5 -5
- package/dist/templates/auth/auth-schema.ts.template +1 -1
- package/dist/templates/auth/auth.ts.template +2 -2
- package/dist/templates/database/db-schema-example.ts.template +1 -1
- package/dist/utils.d.ts +84 -11
- package/dist/utils.js +194 -116
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -213,6 +213,26 @@ Credentials are stored in `~/.playcademy/auth.json`:
|
|
|
213
213
|
|
|
214
214
|
Set up TimeBack LTI integration for your game.
|
|
215
215
|
|
|
216
|
+
**Note:** Before running this command, ensure that `totalXp` is configured for each course in your `playcademy.config.js`:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
integrations: {
|
|
220
|
+
timeback: {
|
|
221
|
+
courses: [
|
|
222
|
+
{
|
|
223
|
+
subject: 'Math',
|
|
224
|
+
grade: 3,
|
|
225
|
+
metadata: {
|
|
226
|
+
metrics: {
|
|
227
|
+
totalXp: 1000, // Required
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
216
236
|
```bash
|
|
217
237
|
playcademy timeback setup
|
|
218
238
|
playcademy timeback setup --dry-run
|
package/dist/constants.d.ts
CHANGED
|
@@ -278,7 +278,7 @@ declare const CLI_FILES: {
|
|
|
278
278
|
declare const DEFAULT_PORTS: {
|
|
279
279
|
/** Sandbox server (mock platform API) */
|
|
280
280
|
readonly SANDBOX: 4321;
|
|
281
|
-
/** Backend dev server (
|
|
281
|
+
/** Backend dev server (project backend with HMR) */
|
|
282
282
|
readonly BACKEND: 8788;
|
|
283
283
|
};
|
|
284
284
|
|
package/dist/constants.js
CHANGED
|
@@ -31,7 +31,6 @@ var package_default = {
|
|
|
31
31
|
sharp: "^0.34.2",
|
|
32
32
|
typedoc: "^0.28.5",
|
|
33
33
|
"typedoc-plugin-markdown": "^4.7.0",
|
|
34
|
-
"typedoc-vitepress-theme": "^1.1.2",
|
|
35
34
|
"typescript-eslint": "^8.30.1",
|
|
36
35
|
"yocto-spinner": "^0.2.2"
|
|
37
36
|
},
|
|
@@ -255,7 +254,7 @@ var CLI_FILES = {
|
|
|
255
254
|
var DEFAULT_PORTS = {
|
|
256
255
|
/** Sandbox server (mock platform API) */
|
|
257
256
|
SANDBOX: 4321,
|
|
258
|
-
/** Backend dev server (
|
|
257
|
+
/** Backend dev server (project backend with HMR) */
|
|
259
258
|
BACKEND: 8788
|
|
260
259
|
};
|
|
261
260
|
|
package/dist/db.js
CHANGED
|
@@ -1458,7 +1458,6 @@ var package_default = {
|
|
|
1458
1458
|
sharp: "^0.34.2",
|
|
1459
1459
|
typedoc: "^0.28.5",
|
|
1460
1460
|
"typedoc-plugin-markdown": "^4.7.0",
|
|
1461
|
-
"typedoc-vitepress-theme": "^1.1.2",
|
|
1462
1461
|
"typescript-eslint": "^8.30.1",
|
|
1463
1462
|
"yocto-spinner": "^0.2.2"
|
|
1464
1463
|
},
|
|
@@ -2017,7 +2016,7 @@ function getRunCommand(pm, script) {
|
|
|
2017
2016
|
}
|
|
2018
2017
|
|
|
2019
2018
|
// src/lib/core/client.ts
|
|
2020
|
-
import { PlaycademyClient } from "@playcademy/sdk";
|
|
2019
|
+
import { PlaycademyClient } from "@playcademy/sdk/internal";
|
|
2021
2020
|
|
|
2022
2021
|
// src/lib/core/context.ts
|
|
2023
2022
|
var context = {};
|
|
@@ -2637,7 +2636,7 @@ import colors3 from "yoctocolors-cjs";
|
|
|
2637
2636
|
|
|
2638
2637
|
// src/lib/core/error.ts
|
|
2639
2638
|
import { bold as bold2, dim as dim2, redBright } from "colorette";
|
|
2640
|
-
import { ApiError, extractApiErrorInfo } from "@playcademy/sdk";
|
|
2639
|
+
import { ApiError, extractApiErrorInfo } from "@playcademy/sdk/internal";
|
|
2641
2640
|
function isConfigError(error) {
|
|
2642
2641
|
return error !== null && typeof error === "object" && "name" in error && error.name === "ConfigError" && "message" in error;
|
|
2643
2642
|
}
|
|
@@ -2749,12 +2748,20 @@ function customTransform(text) {
|
|
|
2749
2748
|
return result;
|
|
2750
2749
|
}
|
|
2751
2750
|
function formatTable(data, title) {
|
|
2751
|
+
const ANSI_REGEX = /\u001B\[[0-9;]*m/g;
|
|
2752
|
+
const stripAnsi2 = (value) => value.replace(ANSI_REGEX, "");
|
|
2753
|
+
const visibleLength = (value) => stripAnsi2(value).length;
|
|
2754
|
+
const padCell = (value, width) => {
|
|
2755
|
+
const length = visibleLength(value);
|
|
2756
|
+
if (length >= width) return value;
|
|
2757
|
+
return value + " ".repeat(width - length);
|
|
2758
|
+
};
|
|
2752
2759
|
if (data.length === 0) return;
|
|
2753
2760
|
const keys = Object.keys(data[0]);
|
|
2754
2761
|
const rows = data.map((item) => keys.map((key) => String(item[key] ?? "")));
|
|
2755
2762
|
const widths = keys.map((key, i) => {
|
|
2756
|
-
const headerWidth = key
|
|
2757
|
-
const dataWidth = Math.max(...rows.map((row) => row[i]
|
|
2763
|
+
const headerWidth = visibleLength(key);
|
|
2764
|
+
const dataWidth = Math.max(...rows.map((row) => visibleLength(row[i])));
|
|
2758
2765
|
return Math.max(headerWidth, dataWidth);
|
|
2759
2766
|
});
|
|
2760
2767
|
const totalWidth = widths.reduce((sum, w) => sum + w + 3, -1);
|
|
@@ -2770,11 +2777,11 @@ function formatTable(data, title) {
|
|
|
2770
2777
|
console.log(titleRow);
|
|
2771
2778
|
console.log(titleSeparator);
|
|
2772
2779
|
}
|
|
2773
|
-
const header = "\u2502 " + keys.map((key, i) => key
|
|
2780
|
+
const header = "\u2502 " + keys.map((key, i) => padCell(key, widths[i])).join(" \u2502 ") + " \u2502";
|
|
2774
2781
|
console.log(header);
|
|
2775
2782
|
console.log(separator);
|
|
2776
2783
|
rows.forEach((row) => {
|
|
2777
|
-
const dataRow = "\u2502 " + row.map((cell, i) => cell
|
|
2784
|
+
const dataRow = "\u2502 " + row.map((cell, i) => padCell(cell, widths[i])).join(" \u2502 ") + " \u2502";
|
|
2778
2785
|
console.log(dataRow);
|
|
2779
2786
|
});
|
|
2780
2787
|
console.log(bottomBorder);
|
|
@@ -33,6 +33,13 @@ export async function registerBuiltinRoutes(app: Hono<HonoEnv>, integrations?: I
|
|
|
33
33
|
])
|
|
34
34
|
app.post(ROUTES.TIMEBACK.END_ACTIVITY, endActivity.POST)
|
|
35
35
|
// ... other routes
|
|
36
|
+
} else if (integrations?.timeback === null) {
|
|
37
|
+
app.post('/api/integrations/timeback/end-activity', async c => {
|
|
38
|
+
return c.json({
|
|
39
|
+
status: 'ok',
|
|
40
|
+
__playcademyDevWarning: 'timeback-not-configured',
|
|
41
|
+
})
|
|
42
|
+
})
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
// TODO: Auth integration
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Context } from 'hono'
|
|
8
|
-
import type {
|
|
8
|
+
import type { PlaycademyConfig } from '@playcademy/sdk/server'
|
|
9
9
|
import type { RouteMetadata } from '../entry/types'
|
|
10
10
|
import type { HonoEnv, ServerEnv } from '../types'
|
|
11
11
|
|
|
@@ -49,16 +49,27 @@ function formatRoutes(routes: RouteMetadata[]): Array<{ path: string; methods: s
|
|
|
49
49
|
/**
|
|
50
50
|
* Get TimeBack debug info if applicable
|
|
51
51
|
*/
|
|
52
|
-
function getTimebackDebugInfo(config?: PlaycademyConfig
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
function getTimebackDebugInfo(config?: PlaycademyConfig) {
|
|
53
|
+
const timeback = config?.integrations?.timeback
|
|
54
|
+
|
|
55
|
+
if (!timeback || !timeback.courses || timeback.courses.length === 0) {
|
|
56
|
+
return {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const grades = Array.from(new Set(timeback.courses.map(c => c.grade))).sort()
|
|
60
|
+
const subjects = Array.from(new Set(timeback.courses.map(c => c.subject))).sort()
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
timeback: {
|
|
64
|
+
courseCount: timeback.courses.length,
|
|
65
|
+
grades,
|
|
66
|
+
subjects,
|
|
67
|
+
},
|
|
55
68
|
}
|
|
56
|
-
return {}
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
export async function GET(c: Context<HonoEnv>): Promise<Response> {
|
|
60
72
|
const config = c.get('config')
|
|
61
|
-
const sdk = c.get('sdk')
|
|
62
73
|
const routeMetadata = c.get('routeMetadata')
|
|
63
74
|
|
|
64
75
|
return c.json({
|
|
@@ -68,6 +79,6 @@ export async function GET(c: Context<HonoEnv>): Promise<Response> {
|
|
|
68
79
|
secrets: getSecretsCount(c.env),
|
|
69
80
|
integrations: getEnabledIntegrations(config),
|
|
70
81
|
routes: formatRoutes(routeMetadata),
|
|
71
|
-
...getTimebackDebugInfo(config
|
|
82
|
+
...getTimebackDebugInfo(config),
|
|
72
83
|
})
|
|
73
84
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { verifyGameToken } from '@playcademy/sdk/server'
|
|
2
|
-
|
|
3
1
|
import type { Context } from 'hono'
|
|
4
2
|
import type { PlaycademyConfig } from '@playcademy/sdk/server'
|
|
5
3
|
import type { ActivityData } from '@playcademy/timeback/types'
|
|
@@ -26,82 +24,148 @@ function getConfig(c: Context<HonoEnv>): PlaycademyConfig {
|
|
|
26
24
|
return config
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
function
|
|
30
|
-
activityData
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
function validateRequestBody(body: {
|
|
28
|
+
activityData?: ActivityData
|
|
29
|
+
scoreData?: { correctQuestions?: number; totalQuestions?: number }
|
|
30
|
+
timingData?: { durationSeconds?: number }
|
|
31
|
+
masteredUnits?: number
|
|
32
|
+
}): { error: string } | null {
|
|
33
|
+
if (!body.activityData?.activityId) {
|
|
34
|
+
return { error: 'activityId is required' }
|
|
35
|
+
}
|
|
36
|
+
if (!body.activityData?.grade) {
|
|
37
|
+
return { error: 'grade is required' }
|
|
38
|
+
}
|
|
39
|
+
if (!body.activityData?.subject) {
|
|
40
|
+
return { error: 'subject is required' }
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
typeof body.scoreData?.correctQuestions !== 'number' ||
|
|
44
|
+
typeof body.scoreData?.totalQuestions !== 'number'
|
|
45
|
+
) {
|
|
46
|
+
return { error: 'correctQuestions and totalQuestions are required' }
|
|
47
|
+
}
|
|
48
|
+
if (typeof body.timingData?.durationSeconds !== 'number') {
|
|
49
|
+
return { error: 'durationSeconds is required' }
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
body.masteredUnits !== undefined &&
|
|
53
|
+
(typeof body.masteredUnits !== 'number' || body.masteredUnits < 0)
|
|
54
|
+
) {
|
|
55
|
+
return { error: 'masteredUnits must be a non-negative number when provided' }
|
|
56
|
+
}
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateCourse(params: {
|
|
61
|
+
grade: number
|
|
62
|
+
subject: string
|
|
63
|
+
config: PlaycademyConfig
|
|
64
|
+
}): { error: string } | null {
|
|
65
|
+
const { grade, subject, config } = params
|
|
66
|
+
const timebackConfig = config.integrations?.timeback
|
|
67
|
+
const configuredCourse = timebackConfig?.courses?.find(
|
|
68
|
+
course => course.grade === grade && course.subject === subject,
|
|
69
|
+
)
|
|
70
|
+
if (!configuredCourse) {
|
|
71
|
+
const configured = timebackConfig?.courses
|
|
72
|
+
?.map(c => `${c.subject} (Grade ${c.grade})`)
|
|
73
|
+
.join(', ')
|
|
74
|
+
return {
|
|
75
|
+
error: `Invalid grade/subject combination: ${subject} (Grade ${grade}). Configured courses: ${configured || 'none'}`,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function enrichActivityData(params: {
|
|
82
|
+
activityData: ActivityData
|
|
83
|
+
config: PlaycademyConfig
|
|
84
|
+
c: Context<HonoEnv>
|
|
85
|
+
}): { data?: ActivityData; error?: string } {
|
|
86
|
+
const { activityData, config, c } = params
|
|
34
87
|
const appName = activityData.appName || config?.name
|
|
35
|
-
const subject =
|
|
36
|
-
activityData.subject ||
|
|
37
|
-
config?.integrations?.timeback?.course?.defaultSubject ||
|
|
38
|
-
config?.integrations?.timeback?.course?.subjects?.[0]
|
|
39
88
|
const sensorUrl = activityData.sensorUrl || new URL(c.req.url).origin
|
|
40
89
|
|
|
41
|
-
if (!appName)
|
|
42
|
-
|
|
43
|
-
|
|
90
|
+
if (!appName) {
|
|
91
|
+
return { error: 'App name is required (missing from activityData and config)' }
|
|
92
|
+
}
|
|
93
|
+
if (!sensorUrl) {
|
|
94
|
+
return { error: 'Sensor URL is required' }
|
|
95
|
+
}
|
|
44
96
|
|
|
45
|
-
return { ...activityData, appName,
|
|
97
|
+
return { data: { ...activityData, appName, sensorUrl } }
|
|
46
98
|
}
|
|
47
99
|
|
|
48
100
|
export async function POST(c: Context<HonoEnv>): Promise<Response> {
|
|
49
101
|
try {
|
|
50
|
-
// 1.
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
53
|
-
return c.json({ error: 'Unauthorized' }, 401)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const { user } = await verifyGameToken(token, { baseUrl: c.env.PLAYCADEMY_BASE_URL })
|
|
102
|
+
// 1. Get authenticated user from middleware
|
|
103
|
+
const user = c.get('playcademyUser')
|
|
104
|
+
if (!user) return c.json({ error: 'Unauthorized' }, 401)
|
|
57
105
|
|
|
58
106
|
// 2. Ensure user has TimeBack integration
|
|
59
107
|
if (!user.timeback_id) {
|
|
60
|
-
|
|
108
|
+
const message = 'User does not have TimeBack integration'
|
|
109
|
+
console.error('[TimeBack End Activity] Error:', message)
|
|
110
|
+
return c.json({ error: message }, 400)
|
|
61
111
|
}
|
|
62
112
|
|
|
63
113
|
// 3. Parse request body
|
|
64
|
-
const { activityData, scoreData, timingData, xpEarned } = await c.req.json()
|
|
114
|
+
const { activityData, scoreData, timingData, xpEarned, masteredUnits } = await c.req.json()
|
|
65
115
|
|
|
66
116
|
// 4. Validate required fields
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return c.json({ error:
|
|
117
|
+
const bodyValidationError = validateRequestBody({
|
|
118
|
+
activityData,
|
|
119
|
+
scoreData,
|
|
120
|
+
timingData,
|
|
121
|
+
masteredUnits,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (bodyValidationError) {
|
|
125
|
+
const message = bodyValidationError.error
|
|
126
|
+
console.error('[TimeBack End Activity] Error:', message)
|
|
127
|
+
return c.json({ error: message }, 400)
|
|
78
128
|
}
|
|
79
129
|
|
|
80
|
-
// 5. Get config
|
|
130
|
+
// 5. Get config
|
|
81
131
|
const config = getConfig(c)
|
|
82
|
-
const enrichedActivityData = enrichActivityData(activityData, config, c)
|
|
83
132
|
|
|
84
|
-
// 6.
|
|
133
|
+
// 6. Validate grade/subject against configured courses
|
|
134
|
+
const { grade, subject } = activityData
|
|
135
|
+
const courseValidationError = validateCourse({ grade, subject, config })
|
|
136
|
+
|
|
137
|
+
if (courseValidationError) {
|
|
138
|
+
const message = courseValidationError.error
|
|
139
|
+
console.error('[TimeBack End Activity] Error:', message)
|
|
140
|
+
return c.json({ error: message }, 400)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 7. Enrich activity data with required Caliper fields
|
|
144
|
+
const enrichResult = enrichActivityData({ activityData, config, c })
|
|
145
|
+
|
|
146
|
+
if (!enrichResult.data) {
|
|
147
|
+
console.error('[TimeBack End Activity] Error:', enrichResult.error)
|
|
148
|
+
return c.json({ error: enrichResult.error }, 500)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 8. Get SDK client from context (initialized once per Worker, reused across requests)
|
|
85
152
|
const sdk = c.get('sdk')
|
|
86
153
|
|
|
87
|
-
//
|
|
154
|
+
// 9. End activity (SDK calculates XP server-side with attempt tracking)
|
|
88
155
|
const result = await sdk.timeback.endActivity(user.timeback_id, {
|
|
89
|
-
activityData:
|
|
156
|
+
activityData: enrichResult.data,
|
|
90
157
|
scoreData,
|
|
91
158
|
timingData,
|
|
92
159
|
xpEarned,
|
|
160
|
+
masteredUnits,
|
|
93
161
|
})
|
|
94
162
|
|
|
95
163
|
return c.json(result)
|
|
96
164
|
} catch (error) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
103
|
-
},
|
|
104
|
-
500,
|
|
105
|
-
)
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
166
|
+
const stack = error instanceof Error ? error.stack : undefined
|
|
167
|
+
if (message) console.error('[TimeBack End Activity] Error:', message)
|
|
168
|
+
if (stack) console.error('[TimeBack End Activity] Stack:', stack)
|
|
169
|
+
return c.json({ error: 'Failed to end activity', message, stack }, 500)
|
|
106
170
|
}
|
|
107
171
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
/// <reference types="@cloudflare/workers-types" />
|
|
9
9
|
|
|
10
|
+
import type { UserInfo } from '@playcademy/data/types'
|
|
10
11
|
import type { PlaycademyClient, PlaycademyConfig } from '@playcademy/sdk/server'
|
|
11
12
|
import type { RouteMetadata } from './entry/types'
|
|
12
13
|
|
|
@@ -70,6 +71,7 @@ export interface HonoVariables {
|
|
|
70
71
|
sdk: PlaycademyClient
|
|
71
72
|
config: PlaycademyConfig
|
|
72
73
|
routeMetadata: Array<RouteMetadata>
|
|
74
|
+
playcademyUser?: UserInfo
|
|
73
75
|
[key: string]: unknown
|
|
74
76
|
}
|
|
75
77
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { UserInfo, ApiKey } from '@playcademy/data/types';
|
|
2
|
+
export { TimebackCourseConfig } from '@playcademy/data/types';
|
|
2
3
|
import { SchemaInfo } from '@playcademy/cloudflare';
|
|
3
|
-
import {
|
|
4
|
-
export { ComponentConfig, ComponentResourceConfig,
|
|
5
|
-
import { PlaycademyClient } from '@playcademy/sdk';
|
|
4
|
+
import { CourseConfig, OrganizationConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig } from '@playcademy/timeback/types';
|
|
5
|
+
export { ComponentConfig, ComponentResourceConfig, OrganizationConfig, ResourceConfig, TimebackGrade, TimebackSourcedIds, TimebackSubject } from '@playcademy/timeback/types';
|
|
6
|
+
import { PlaycademyClient } from '@playcademy/sdk/internal';
|
|
6
7
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -243,6 +244,20 @@ interface BulkCollectionResult {
|
|
|
243
244
|
totalSize: number;
|
|
244
245
|
}
|
|
245
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Minimal course configuration for TimeBack integration (used in user-facing config).
|
|
249
|
+
*
|
|
250
|
+
* NOTE: Per-course overrides (title, courseCode, level, metadata) are defined
|
|
251
|
+
* in @playcademy/sdk/server as TimebackCourseConfigWithOverrides.
|
|
252
|
+
* This base type only includes the minimal required fields.
|
|
253
|
+
*
|
|
254
|
+
* For totalXp, use metadata.metrics.totalXp (aligns with upstream TimeBack structure).
|
|
255
|
+
*/
|
|
256
|
+
type TimebackCourseConfig = {
|
|
257
|
+
subject: string;
|
|
258
|
+
grade: number;
|
|
259
|
+
};
|
|
260
|
+
|
|
246
261
|
/**
|
|
247
262
|
* @fileoverview Server SDK Type Definitions
|
|
248
263
|
*
|
|
@@ -251,20 +266,50 @@ interface BulkCollectionResult {
|
|
|
251
266
|
*/
|
|
252
267
|
|
|
253
268
|
/**
|
|
254
|
-
* TimeBack integration
|
|
269
|
+
* Base configuration for TimeBack integration (shared across all courses).
|
|
270
|
+
* References upstream TimeBack types from @playcademy/timeback.
|
|
271
|
+
*
|
|
272
|
+
* All fields are optional and support template variables: {grade}, {subject}, {gameSlug}
|
|
255
273
|
*/
|
|
256
|
-
interface
|
|
257
|
-
/** Organization
|
|
274
|
+
interface TimebackBaseConfig {
|
|
275
|
+
/** Organization configuration (shared across all courses) */
|
|
258
276
|
organization?: Partial<OrganizationConfig>;
|
|
259
|
-
/** Course
|
|
260
|
-
course
|
|
261
|
-
/** Component
|
|
277
|
+
/** Course defaults (can be overridden per-course) */
|
|
278
|
+
course?: Partial<CourseConfig>;
|
|
279
|
+
/** Component defaults */
|
|
262
280
|
component?: Partial<ComponentConfig>;
|
|
263
|
-
/** Resource
|
|
281
|
+
/** Resource defaults */
|
|
264
282
|
resource?: Partial<ResourceConfig>;
|
|
265
|
-
/**
|
|
283
|
+
/** ComponentResource defaults */
|
|
266
284
|
componentResource?: Partial<ComponentResourceConfig>;
|
|
267
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Extended course configuration that merges TimebackCourseConfig with per-course overrides.
|
|
288
|
+
* Used in playcademy.config.* to allow per-course customization.
|
|
289
|
+
*/
|
|
290
|
+
interface TimebackCourseConfigWithOverrides extends TimebackCourseConfig {
|
|
291
|
+
title?: string;
|
|
292
|
+
courseCode?: string;
|
|
293
|
+
level?: string;
|
|
294
|
+
metadata?: CourseConfig['metadata'];
|
|
295
|
+
totalXp?: number | null;
|
|
296
|
+
masterableUnits?: number | null;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* TimeBack integration configuration for Playcademy config file.
|
|
300
|
+
*
|
|
301
|
+
* Supports two levels of customization:
|
|
302
|
+
* 1. `base`: Shared defaults for all courses (organization, course, component, resource, componentResource)
|
|
303
|
+
* 2. Per-course overrides in the `courses` array (title, courseCode, level, gradingScheme, metadata)
|
|
304
|
+
*
|
|
305
|
+
* Template variables ({grade}, {subject}, {gameSlug}) can be used in string fields.
|
|
306
|
+
*/
|
|
307
|
+
interface TimebackIntegrationConfig {
|
|
308
|
+
/** Multi-grade course configuration (array of grade/subject/totalXp with optional per-course overrides) */
|
|
309
|
+
courses: TimebackCourseConfigWithOverrides[];
|
|
310
|
+
/** Optional base configuration (shared across all courses, can be overridden per-course) */
|
|
311
|
+
base?: TimebackBaseConfig;
|
|
312
|
+
}
|
|
268
313
|
/**
|
|
269
314
|
* Custom API routes integration
|
|
270
315
|
*/
|
|
@@ -285,7 +330,7 @@ interface DatabaseIntegration {
|
|
|
285
330
|
*/
|
|
286
331
|
interface IntegrationsConfig {
|
|
287
332
|
/** TimeBack integration (optional) */
|
|
288
|
-
timeback?: TimebackIntegrationConfig;
|
|
333
|
+
timeback?: TimebackIntegrationConfig | null;
|
|
289
334
|
/** Custom API routes (optional) */
|
|
290
335
|
customRoutes?: CustomRoutesIntegration | boolean;
|
|
291
336
|
/** Database (optional) */
|
|
@@ -653,7 +698,7 @@ interface IntegrationChangeDetector<TConfig = unknown> {
|
|
|
653
698
|
* Called automatically during deployment when metadata has changed
|
|
654
699
|
* Implementation should: fetch current config, detect changes, update if needed
|
|
655
700
|
*
|
|
656
|
-
* @param gameId - The
|
|
701
|
+
* @param gameId - The project ID
|
|
657
702
|
* @param client - Playcademy API client
|
|
658
703
|
* @param localConfig - Full local PlaycademyConfig
|
|
659
704
|
* @param verbose - Verbose logging flag
|
|
@@ -801,17 +846,17 @@ interface DeploymentResult {
|
|
|
801
846
|
/**
|
|
802
847
|
* Deployment configuration
|
|
803
848
|
*
|
|
804
|
-
* Configuration for deploying a
|
|
849
|
+
* Configuration for deploying a project to Playcademy.
|
|
805
850
|
* Can be provided via CLI options, playcademy.json file, or package.json.
|
|
806
851
|
*/
|
|
807
852
|
interface DeployConfig {
|
|
808
|
-
/**
|
|
853
|
+
/** Project slug (URL-friendly identifier) */
|
|
809
854
|
slug?: string;
|
|
810
|
-
/** Display name for the
|
|
855
|
+
/** Display name for the project */
|
|
811
856
|
displayName?: string;
|
|
812
|
-
/**
|
|
857
|
+
/** Project description */
|
|
813
858
|
description?: string;
|
|
814
|
-
/** Emoji icon for the
|
|
859
|
+
/** Emoji icon for the project */
|
|
815
860
|
emoji?: string;
|
|
816
861
|
/** Path to the build directory or zip file */
|
|
817
862
|
buildPath?: string;
|
|
@@ -935,6 +980,21 @@ interface CallbackServerResult {
|
|
|
935
980
|
error?: string;
|
|
936
981
|
}
|
|
937
982
|
|
|
983
|
+
/**
|
|
984
|
+
* @fileoverview Config Loader Types
|
|
985
|
+
*
|
|
986
|
+
* Types for the config loader functionality.
|
|
987
|
+
*/
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Result of loading config with path information
|
|
991
|
+
*/
|
|
992
|
+
interface LoadConfigResult {
|
|
993
|
+
config: PlaycademyConfig;
|
|
994
|
+
configPath: string;
|
|
995
|
+
configDir: string;
|
|
996
|
+
}
|
|
997
|
+
|
|
938
998
|
/**
|
|
939
999
|
* Preview options
|
|
940
1000
|
*/
|
|
@@ -1083,4 +1143,4 @@ interface KeyMetadata {
|
|
|
1083
1143
|
valueType?: 'json' | 'string';
|
|
1084
1144
|
}
|
|
1085
1145
|
|
|
1086
|
-
export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, AuthStrategy, BackendBundle, BackendDeploymentMetadata, BackendDiff, BackendFeatures, BucketBulkOptions, BucketDeleteOptions, BucketGetOptions, BucketListOptions, BucketPutOptions, BuildDiff, BulkCollectionResult, BundleOptions, CallbackServerResult, CollectedFile, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DatabaseIntegrationOptions, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, DevServerOptions, EmbeddedSourcePaths, EnvironmentAuthProfiles, GameStore, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, KeyMetadata, KeyStats, LoginCredentials, LoginResponse, PlaycademyConfig, PluginLogger, PreviewOptions, PreviewResponse, SecretsDiff, SignInResponse, SsoCallbackData, TimebackIntegrationConfig, TokenType, UpdateExistingGameOptions };
|
|
1146
|
+
export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, AuthStrategy, BackendBundle, BackendDeploymentMetadata, BackendDiff, BackendFeatures, BucketBulkOptions, BucketDeleteOptions, BucketGetOptions, BucketListOptions, BucketPutOptions, BuildDiff, BulkCollectionResult, BundleOptions, CallbackServerResult, CollectedFile, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DatabaseIntegrationOptions, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, DevServerOptions, EmbeddedSourcePaths, EnvironmentAuthProfiles, GameStore, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, KeyMetadata, KeyStats, LoadConfigResult, LoginCredentials, LoginResponse, PlaycademyConfig, PluginLogger, PreviewOptions, PreviewResponse, SecretsDiff, SignInResponse, SsoCallbackData, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackIntegrationConfig, TokenType, UpdateExistingGameOptions };
|