units,lesson as standalone

This commit is contained in:
2026-07-10 11:44:46 +08:00
parent 86fba50b95
commit e1ffdab190
46 changed files with 1463 additions and 197 deletions
+12
View File
@@ -105,6 +105,16 @@ async function onPerfectQuiz(user_id, quiz_id) {
await grantAchievement(user_id, 'perfect_quiz_score', { quiz_id });
}
/** Call after a single task's requirements are all satisfied for a user */
async function onTaskCompleted(user_id, task_id, task_name = null) {
await grantAchievement(user_id, 'first_task_completed', { task_id, task_name });
}
/** Call after every task in a task list is complete for a user */
async function onTaskListCompleted(user_id, task_list_id, task_list_name = null) {
await grantAchievement(user_id, 'task_list_finisher', { task_list_id, task_list_name });
}
/** Call after profile is fully filled out */
async function onProfileCompleted(user_id) {
await grantAchievement(user_id, 'profile_completed');
@@ -151,6 +161,8 @@ module.exports = {
onPerfectQuiz,
onProfileCompleted,
onReferral,
onTaskCompleted,
onTaskListCompleted,
// Admin
adminGrantAchievement,
+11 -10
View File
@@ -159,18 +159,18 @@ async function deleteFile(key) {
// For server-side reads only (e.g. media.controller.js streamAsset piping
// bytes to the browser itself) — never hand this URL to a browser directly.
//
// Must NOT be signed against S3_PUBLIC_URL: that host is fronted by
// garage-anon-proxy, which re-signs every request itself (header-based SigV4,
// real credentials) regardless of any query-string signature already present.
// A presigned URL arriving there collides with the proxy's own signature and
// Garage rejects the request (400 "Header `x-amz-date` should be signed").
// See getPublicUrl() below for the browser-facing equivalent.
// TEMPORARY:
// The problem is this line do not tell for each of development machines
// which ones should be called whether it's for PUBLIC_URL or LOCAL_ENDPOINT itself.
// Must NOT be signed against S3_PUBLIC_URL when Garage is in use: that host is
// fronted by garage-anon-proxy, which re-signs every request itself (header-based
// SigV4, real credentials) regardless of any query-string signature already
// present. A presigned URL arriving there collides with the proxy's own
// signature and Garage rejects the request (400 "Header `x-amz-date` should
// be signed").
//
// Uses the internal `s3` client (always S3_ENDPOINT) when available; falls back
// to getPublicClient() for external S3-compatible services without Garage.
//
async function getSignedDownloadUrl(key, expiresInSeconds = 3600) {
const client = getPublicClient();
const client = process.env.S3_ENDPOINT ? s3 : getPublicClient();
return getSignedUrl(
client,
@@ -183,6 +183,7 @@ async function getSignedDownloadUrl(key, expiresInSeconds = 3600) {
}
);
}
// ─── getPublicUrl ─────────────────────────────────────────────────────────────
//
// Browser-facing pre-signed GET URL, signed against S3_PUBLIC_URL. Safe again