-
Notifications
You must be signed in to change notification settings - Fork 1
Fix 342 Pair Programming Bug Fixes #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
75eb091
4bbba53
f3a6a0c
6738889
67c1f41
f5b9344
aac4439
0a5bc73
d9b68bf
f710d10
ac70956
2615919
3e43601
467c2ce
5e0c4f0
d65cdc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,17 @@ | |
| const { assertStableEmailTemplateContent } = require("../../utils/helper/templateResolver"); | ||
|
|
||
| const MAIL_SERVICE_KEY_PREFIX = "system.mailService."; | ||
| const PRESERVE_WHITESPACE_SETTING_TYPES = new Set(["edits", "text"]); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /** | ||
| * Returns whether a setting value should be trimmed before saving. | ||
| * | ||
| * @param {Object} setting setting entry | ||
| * @returns {boolean} | ||
| */ | ||
| function shouldTrimSetting(setting) { | ||
| return !PRESERVE_WHITESPACE_SETTING_TYPES.has(setting?.type); | ||
| } | ||
|
|
||
| /** | ||
| * Reject email.template.* settings that point at a missing or incomplete template. | ||
|
|
@@ -66,16 +77,49 @@ function payloadTouchesMailService(settings) { | |
| * Normalize setting values to string payload format expected by the settings model. | ||
| * | ||
| * @param {*} value setting value | ||
| * @param {Object} [setting] setting entry | ||
| * @returns {string} | ||
| */ | ||
| function normalizeSettingValue(value) { | ||
| function normalizeSettingValue(value, setting = {}) { | ||
| let normalized; | ||
| if (value === null || value === undefined) { | ||
| return ""; | ||
| normalized = ""; | ||
| } else if (typeof value === "object") { | ||
| // NOTE: Coerce object/array payloads to JSON; persisted settings are always strings. | ||
| normalized = JSON.stringify(value); | ||
| } else { | ||
| normalized = String(value); | ||
| } | ||
| if (typeof value === "object") { | ||
| return JSON.stringify(value); | ||
| return shouldTrimSetting(setting) ? normalized.trim() : normalized; | ||
| } | ||
|
|
||
| /** | ||
| * Load persisted setting types for payload entries that do not include type metadata. | ||
| * | ||
| * @param {Object} Setting setting model | ||
| * @param {Object[]} settings setting entries | ||
| * @param {Object} [options] additional options | ||
| * @returns {Promise<Map<string, string>>} | ||
| */ | ||
| async function getSettingTypeByKey(Setting, settings, options = {}) { | ||
| if (typeof Setting.findAll !== "function") { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both callers of |
||
| return new Map(); | ||
| } | ||
|
|
||
| const keys = [...new Set(settings | ||
| .filter((setting) => setting && typeof setting.key === "string" && !setting.type) | ||
| .map((setting) => setting.key))]; | ||
| if (!keys.length) { | ||
| return new Map(); | ||
| } | ||
| return String(value); | ||
|
|
||
| const rows = await Setting.findAll({ | ||
| where: { key: keys }, | ||
| attributes: ["key", "type"], | ||
| raw: true, | ||
| transaction: options.transaction, | ||
| }); | ||
| return new Map(rows.map((row) => [row.key, row.type])); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -96,19 +140,26 @@ async function saveSettings(Setting, settings, options = {}) { | |
| await validateEmailTemplateSettings(options.models, list, options); | ||
| } | ||
| const touchesMailService = payloadTouchesMailService(list); | ||
| const settingTypeByKey = await getSettingTypeByKey(Setting, list, options); | ||
| for (const setting of list) { | ||
| if (!setting || typeof setting.key !== "string" || setting.key.trim() === "") { | ||
| continue; | ||
| } | ||
| await Setting.set(setting.key, normalizeSettingValue(setting.value), { | ||
| const settingWithType = setting.type ? setting : { | ||
| ...setting, | ||
| type: settingTypeByKey.get(setting.key), | ||
| }; | ||
| await Setting.set(setting.key, normalizeSettingValue(setting.value, settingWithType), { | ||
| transaction: options.transaction, | ||
| }); | ||
| } | ||
| return { touchesMailService }; | ||
| } | ||
|
|
||
| module.exports = { | ||
| getSettingTypeByKey, | ||
| payloadTouchesMailService, | ||
| normalizeSettingValue, | ||
| saveSettings, | ||
| shouldTrimSetting, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,14 +77,20 @@ Before using this feature, make sure Moodle API access is configured as describe | |
| User data can be imported from Moodle using the course ID: | ||
|
|
||
| 1. In the Dashboard navigate to ``Users > Import via Moodle`` | ||
| 2. CARE will match users by email address | ||
| 2. After CARE retrieves the Moodle users, review the role mapping step | ||
| 3. Map each Moodle role label to the corresponding CARE role, or choose ``Do not assign additional role`` if the role should not grant additional CARE permissions | ||
| 4. CARE will match users by email address | ||
|
|
||
| CARE handles three scenarios: | ||
|
|
||
| - New users are created if not found | ||
| - Duplicate users are merged based on email match | ||
| - Conflicts (e.g., mismatched emails) require manual correction | ||
|
|
||
| Moodle role labels can vary by Moodle instance and may include multilingual markup such as ``{mlang de}Lehrende{mlang}{mlang other}Lecturer{mlang}``. CARE hides this markup in the mapping step while keeping the original Moodle role string for the import. | ||
|
|
||
| CSV user import also includes a role mapping step. CARE reads the distinct values from the CSV ``roles`` column and asks you to map each value to a CARE role before previewing the import. Multiple roles in one CSV cell should be separated by commas. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CARE docs name the role instead of addressing the reader, so "asks you to map" does not match the rest of this page. Please write "asks the admin to map each value to a CARE role". |
||
|
|
||
| .. warning:: | ||
|
|
||
| Never delete a user with an ``extId`` unless you are certain it won't be needed. This could prevent future updates or synchronization. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
roleMapcomes from the client, andassignUserRolesinbackend/db/models/user.jsassigns any role name it gets. A caller can sendroleMap: {"Student*in": "admin"}together with an existing user's email and make that account an admin, becauseuserBulkCreatehas noisAdmin()check (the other handlers in this class do have one). Please guard the handler withif (!(await this.isAdmin())) throw ...and drop any mapped value that is not in the allowed role list, since the "no admin" filter inRoleMappingStep.vueonly applies to the dropdown.